From e5b1a7d4237a9006b6c49c9c1c13b292ca4ecf7c Mon Sep 17 00:00:00 2001 From: Teruaki Kawashima Date: Sun, 21 Nov 2010 13:47:56 +0000 Subject: FS#6321: Universal Image Viewer This unifies jpeg viewer, png viewer, and bmp viewer to one plugin, image viewer, so that you can navigate through different image formats. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28626 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/CATEGORIES | 1 + apps/plugins/imageviewer/SOURCES | 2 + apps/plugins/imageviewer/bmp/SOURCES | 1 - apps/plugins/imageviewer/bmp/bmp.c | 47 ++++--- apps/plugins/imageviewer/bmp/bmp.make | 14 +- apps/plugins/imageviewer/bmp/bmp_ui.c | 5 - apps/plugins/imageviewer/image_decoder.c | 130 ++++++++++++++++++ apps/plugins/imageviewer/image_decoder.h | 46 +++++++ apps/plugins/imageviewer/imageviewer.c | 219 ++++++++++++++++++------------ apps/plugins/imageviewer/imageviewer.h | 104 ++++++++++---- apps/plugins/imageviewer/imageviewer.make | 45 +++++- apps/plugins/imageviewer/jpeg/SOURCES | 1 - apps/plugins/imageviewer/jpeg/jpeg.c | 52 ++++--- apps/plugins/imageviewer/jpeg/jpeg.make | 14 +- apps/plugins/imageviewer/jpeg/jpeg_ui.c | 5 - apps/plugins/imageviewer/png/SOURCES | 1 - apps/plugins/imageviewer/png/png.c | 55 ++++---- apps/plugins/imageviewer/png/png.make | 13 +- apps/plugins/imageviewer/png/png_ui.c | 5 - apps/plugins/plugin.lds | 3 + apps/plugins/viewers.config | 10 +- manual/plugins/bmpviewer.tex | 115 ---------------- manual/plugins/imageviewer.tex | 142 +++++++++++++++++++ manual/plugins/jpegviewer.tex | 126 ----------------- manual/plugins/main.tex | 12 +- manual/plugins/pngviewer.tex | 113 --------------- tools/buildzip.pl | 7 +- 27 files changed, 696 insertions(+), 592 deletions(-) create mode 100644 apps/plugins/imageviewer/SOURCES delete mode 100644 apps/plugins/imageviewer/bmp/bmp_ui.c create mode 100644 apps/plugins/imageviewer/image_decoder.c create mode 100644 apps/plugins/imageviewer/image_decoder.h delete mode 100644 apps/plugins/imageviewer/jpeg/jpeg_ui.c delete mode 100644 apps/plugins/imageviewer/png/png_ui.c delete mode 100755 manual/plugins/bmpviewer.tex create mode 100644 manual/plugins/imageviewer.tex delete mode 100644 manual/plugins/jpegviewer.tex delete mode 100644 manual/plugins/pngviewer.tex diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 1d9aa8aee0..983e52c88a 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -35,6 +35,7 @@ frotz,viewers goban,games greyscale,demos helloworld,demos +imageviewer,viewers invadrox,games iriver_flash,apps iriverify,viewers diff --git a/apps/plugins/imageviewer/SOURCES b/apps/plugins/imageviewer/SOURCES new file mode 100644 index 0000000000..610087bf82 --- /dev/null +++ b/apps/plugins/imageviewer/SOURCES @@ -0,0 +1,2 @@ +imageviewer.c +image_decoder.c diff --git a/apps/plugins/imageviewer/bmp/SOURCES b/apps/plugins/imageviewer/bmp/SOURCES index a50d245846..29297c9ec2 100644 --- a/apps/plugins/imageviewer/bmp/SOURCES +++ b/apps/plugins/imageviewer/bmp/SOURCES @@ -1,2 +1 @@ -bmp_ui.c bmp.c diff --git a/apps/plugins/imageviewer/bmp/bmp.c b/apps/plugins/imageviewer/bmp/bmp.c index 6d33575f98..b7efbb7e2c 100644 --- a/apps/plugins/imageviewer/bmp/bmp.c +++ b/apps/plugins/imageviewer/bmp/bmp.c @@ -65,18 +65,13 @@ struct bitmap bmp; /************************* Implementation ***************************/ -bool img_ext(const char *ext) -{ - if (!ext) - return false; - if (!rb->strcasecmp(ext,".bmp")) - return true; - else - return false; -} +#if defined(USEGSLIB) && (CONFIG_PLATFORM & PLATFORM_HOSTED) +/* hack: fix error "undefined reference to `_grey_info'". */ +GREY_INFO_STRUCT +#endif /* USEGSLIB */ -void draw_image_rect(struct image_info *info, - int x, int y, int width, int height) +static void draw_image_rect(struct image_info *info, + int x, int y, int width, int height) { struct t_disp* pdisp = (struct t_disp*)info->data; #ifdef HAVE_LCD_COLOR @@ -95,7 +90,7 @@ void draw_image_rect(struct image_info *info, #endif } -int img_mem(int ds) +static int img_mem(int ds) { #ifndef USEGSLIB return (bmp.width/ds) * (bmp.height/ds) * sizeof (fb_data); @@ -104,8 +99,8 @@ int img_mem(int ds) #endif } -int load_image(char *filename, struct image_info *info, - unsigned char *buf, ssize_t *buf_size) +static int load_image(char *filename, struct image_info *info, + unsigned char *buf, ssize_t *buf_size) { int w, h; /* used to center output */ long time; /* measured ticks */ @@ -147,7 +142,7 @@ int load_image(char *filename, struct image_info *info, } #endif #ifdef USE_PLUG_BUF - if (!plug_buf) + if (!iv->plug_buf) #endif { while (size > *buf_size && bmp.width >= 2 && bmp.height >= 2 && ds < 8) @@ -174,7 +169,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_OUTOFMEM; } - if (!running_slideshow) + if (!iv->running_slideshow) { rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1); rb->lcd_putsf(0, 1, "loading %dx%d%s", @@ -204,7 +199,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_ERROR; } - if (!running_slideshow) + if (!iv->running_slideshow) { rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ); rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */ @@ -212,7 +207,7 @@ int load_image(char *filename, struct image_info *info, rb->lcd_update(); } #ifdef DISK_SPINDOWN - else if (immediate_ata_off) + else if(iv->immediate_ata_off) { /* running slideshow and time is long enough: power down disk */ rb->storage_sleep(); @@ -223,7 +218,7 @@ int load_image(char *filename, struct image_info *info, buf_images = buf_root = buf + size; buf_images_size = root_size = *buf_size - size; - if (!running_slideshow) + if (!iv->running_slideshow) { rb->lcd_putsf(0, 2, "image %dx%d", bmp.width, bmp.height); rb->lcd_update(); @@ -235,7 +230,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_OK; } -int get_image(struct image_info *info, int ds) +static int get_image(struct image_info *info, int ds) { struct t_disp* p_disp = &disp[ds]; /* short cut */ @@ -270,7 +265,7 @@ int get_image(struct image_info *info, int ds) buf_images += size; buf_images_size -= size; - if (!running_slideshow) + if (!iv->running_slideshow) { rb->lcd_putsf(0, 3, "resizing %d*%d", info->width, info->height); rb->lcd_update(); @@ -294,3 +289,13 @@ int get_image(struct image_info *info, int ds) return PLUGIN_OK; } + +const struct image_decoder image_decoder = { + true, + img_mem, + load_image, + get_image, + draw_image_rect, +}; + +IMGDEC_HEADER diff --git a/apps/plugins/imageviewer/bmp/bmp.make b/apps/plugins/imageviewer/bmp/bmp.make index 0582ba3eb3..0947dd6166 100644 --- a/apps/plugins/imageviewer/bmp/bmp.make +++ b/apps/plugins/imageviewer/bmp/bmp.make @@ -10,12 +10,18 @@ BMPSRCDIR := $(IMGVSRCDIR)/bmp BMPBUILDDIR := $(IMGVBUILDDIR)/bmp -ROCKS += $(BMPBUILDDIR)/bmp.rock - BMP_SRC := $(call preprocess, $(BMPSRCDIR)/SOURCES) BMP_OBJ := $(call c2obj, $(BMP_SRC)) -# add source files to OTHER_SRC to get automatic dependencies OTHER_SRC += $(BMP_SRC) -$(BMPBUILDDIR)/bmp.rock: $(BMP_OBJ) +ROCKS += $(BMPBUILDDIR)/bmp.ovl + +$(BMPBUILDDIR)/bmp.refmap: $(BMP_OBJ) +$(BMPBUILDDIR)/bmp.link: $(PLUGIN_LDS) $(BMPBUILDDIR)/bmp.refmap +$(BMPBUILDDIR)/bmp.ovl: $(BMP_OBJ) + +# special pattern rule for compiling image decoder with extra flags +$(BMPBUILDDIR)/%.o: $(BMPSRCDIR)/%.c $(BMPSRCDIR)/bmp.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(IMGDECFLAGS) -c $< -o $@ diff --git a/apps/plugins/imageviewer/bmp/bmp_ui.c b/apps/plugins/imageviewer/bmp/bmp_ui.c deleted file mode 100644 index 8ff3e0c880..0000000000 --- a/apps/plugins/imageviewer/bmp/bmp_ui.c +++ /dev/null @@ -1,5 +0,0 @@ -#define BMP_VIEWER -#define MENU_TITLE "BMP Menu" -#define UNSCALED_IS_AVAILABLE 1 - -#include "../imageviewer.c" diff --git a/apps/plugins/imageviewer/image_decoder.c b/apps/plugins/imageviewer/image_decoder.c new file mode 100644 index 0000000000..b4fa27e425 --- /dev/null +++ b/apps/plugins/imageviewer/image_decoder.c @@ -0,0 +1,130 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * load image decoder. + * + * 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 "imageviewer.h" +#include "image_decoder.h" + +static const char *decoder_names[MAX_IMAGE_TYPES] = { + "bmp", + "jpeg", + "png", +}; + +/* check file type by extention */ +enum image_type get_image_type(const char *name) +{ + static const struct { + char *ext; + enum image_type type; + } ext_list[] = { + { ".bmp", IMAGE_BMP }, + { ".jpg", IMAGE_JPEG }, + { ".jpe", IMAGE_JPEG }, + { ".jpeg", IMAGE_JPEG }, + { ".png", IMAGE_PNG }, + }; + + const char *ext = rb->strrchr(name, '.'); + int i; + if (!ext) + return IMAGE_UNKNOWN; + + for (i = 0; i < (int)ARRAYLEN(ext_list); i++) + { + if (!rb->strcasecmp(ext, ext_list[i].ext)) + return ext_list[i].type; + } + return IMAGE_UNKNOWN; +} + +static void *decoder_handle = NULL; +const struct image_decoder *load_decoder(struct loader_info *loader_info) +{ + const char *name; + char filename[MAX_PATH]; + struct imgdec_header *hdr; + struct lc_header *lc_hdr; + + if (loader_info->type < 0 || loader_info->type >= MAX_IMAGE_TYPES) + { + rb->splashf(2*HZ, "Unknown type: %d", loader_info->type); + goto error; + } + + release_decoder(); + + name = decoder_names[loader_info->type]; + rb->snprintf(filename, MAX_PATH, VIEWERS_DIR "/%s.ovl", name); + + /* load decoder to the buffer. */ + decoder_handle = rb->lc_open(filename, loader_info->buffer, loader_info->size); + if (!decoder_handle) + { + rb->splashf(2*HZ, "Can't open %s", filename); + goto error; + } + + hdr = rb->lc_get_header(decoder_handle); + if (!hdr) + { + rb->splash(2*HZ, "Can't get header"); + goto error_close; + } + lc_hdr = &hdr->lc_hdr; + + if (lc_hdr->magic != PLUGIN_MAGIC || lc_hdr->target_id != TARGET_ID) + { + rb->splashf(2*HZ, "%s decoder: Incompatible model.", name); + goto error_close; + } + + if (lc_hdr->api_version != IMGDEC_API_VERSION) + { + rb->splashf(2*HZ, "%s decoder: Incompatible version.", name); + goto error_close; + } + + *(hdr->api) = rb; + *(hdr->img_api) = loader_info->iv; + + /* set remaining buffer size to loader_info. decoder will + * be loaded to the end of the buffer, so fix size only. */ +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + loader_info->size = lc_hdr->load_addr - loader_info->buffer; +#endif + + return hdr->decoder; + +error_close: + release_decoder(); +error: + return NULL; +} + +void release_decoder(void) +{ + if (decoder_handle != NULL) + { + rb->lc_close(decoder_handle); + decoder_handle = NULL; + } +} diff --git a/apps/plugins/imageviewer/image_decoder.h b/apps/plugins/imageviewer/image_decoder.h new file mode 100644 index 0000000000..93608b1b9a --- /dev/null +++ b/apps/plugins/imageviewer/image_decoder.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * load image decoder. + * + * 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. + * + ****************************************************************************/ + +#ifndef _IMAGE_DECODER_H +#define _IMAGE_DECODER_H + +#include "imageviewer.h" + +enum image_type { + IMAGE_UNKNOWN = -1, + IMAGE_BMP = 0, + IMAGE_JPEG, + IMAGE_PNG, + MAX_IMAGE_TYPES +}; + +struct loader_info { + enum image_type type; + const struct imgdec_api *iv; + unsigned char* buffer; + size_t size; +}; + +enum image_type get_image_type(const char *name); +const struct image_decoder *load_decoder(struct loader_info *loader_info); +void release_decoder(void); + +#endif /* _IMAGE_DECODER_H */ diff --git a/apps/plugins/imageviewer/imageviewer.c b/apps/plugins/imageviewer/imageviewer.c index e3c72ae291..01b9f31be1 100644 --- a/apps/plugins/imageviewer/imageviewer.c +++ b/apps/plugins/imageviewer/imageviewer.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * user intereface of image viewers (jpeg, png, etc.) + * user intereface of image viewer * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,11 +19,16 @@ * ****************************************************************************/ +/* + * TODO: + * - check magick value in file header to determine image type. + */ #include "plugin.h" #include #include #include #include "imageviewer.h" +#include "image_decoder.h" @@ -38,16 +43,6 @@ GREY_INFO_STRUCT /******************************* Globals ***********************************/ -bool slideshow_enabled = false; /* run slideshow */ -bool running_slideshow = false; /* loading image because of slideshow */ -#ifdef DISK_SPINDOWN -bool immediate_ata_off = false; /* power down disk after loading */ -#endif -#ifdef USE_PLUG_BUF -/* are we using the plugin buffer or the audio buffer? */ -bool plug_buf = true; -#endif - /* Persistent configuration */ #define IMGVIEW_CONFIGFILE "imageviewer.cfg" #define IMGVIEW_SETTINGS_MINVERSION 1 @@ -63,8 +58,7 @@ bool plug_buf = true; #include "jpeg/yuv2rgb.h" #endif -/* jpeg use this */ -struct imgview_settings settings = +static struct imgview_settings settings = { #ifdef HAVE_LCD_COLOR COLOURMODE_COLOUR, @@ -86,17 +80,39 @@ static struct configdata config[] = { .int_p = &settings.ss_timeout }, "Slideshow Time", NULL }, }; +static void cb_progress(int current, int total); + +static struct imgdec_api iv_api = { + .settings = &settings, + .slideshow_enabled = false, + .running_slideshow = false, +#ifdef DISK_SPINDOWN + .immediate_ata_off = false, +#endif +#ifdef USE_PLUG_BUF + .plug_buf = true, +#endif + + .cb_progress = cb_progress, + +#ifdef USEGSLIB + .gray_bitmap_part = myxlcd_ub_(gray_bitmap_part), +#endif +}; + /**************** begin Application ********************/ /************************* Globals ***************************/ -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) +#ifdef HAVE_LCD_COLOR static fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when DITHER_DIFFUSION is set */ #endif -/* my memory pool (from the mp3 buffer) */ +/* buffer to load image decoder */ +static unsigned char* decoder_buf; +static size_t decoder_buf_size; /* the remaining free part of the buffer for loaded+resized images */ static unsigned char* buf; static size_t buf_size; @@ -106,11 +122,14 @@ static struct image_info image_info; /* the current full file name */ static char np_file[MAX_PATH]; -static int curfile = 0, direction = DIR_NEXT, entries = 0; +static int curfile = -1, direction = DIR_NEXT, entries = 0; /* list of the supported image files */ static char **file_pt; +static const struct image_decoder *imgdec = NULL; +static enum image_type image_type = IMAGE_UNKNOWN; + /************************* Implementation ***************************/ /* Read directory contents for scrolling. */ @@ -130,7 +149,7 @@ static void get_pic_list(void) for (i = 0; i < tree->filesindir && buf_size > sizeof(char**); i++) { if (!(dircache[i].attr & ATTR_DIRECTORY) - && img_ext(rb->strrchr(dircache[i].name,'.'))) + && get_image_type(dircache[i].name) != IMAGE_UNKNOWN) { file_pt[entries] = dircache[i].name; /* Set Selected File. */ @@ -187,11 +206,11 @@ static void cleanup(void *parameter) #endif } -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) +#ifdef HAVE_LCD_COLOR static bool set_option_grayscale(void) { bool gray = settings.jpeg_colour_mode == COLOURMODE_GRAY; - rb->set_bool("Grayscale", &gray); + rb->set_bool("Grayscale (Jpeg)", &gray); settings.jpeg_colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR; return false; } @@ -204,14 +223,14 @@ static bool set_option_dithering(void) [DITHER_DIFFUSION] = { "Diffusion", -1 }, }; - rb->set_option("Dithering", &settings.jpeg_dither_mode, INT, + rb->set_option("Dithering (Jpeg)", &settings.jpeg_dither_mode, INT, dithering, DITHER_NUM_MODES, NULL); return false; } -MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale", +MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale (Jpeg)", set_option_grayscale, NULL, NULL, Icon_NOICON); -MENUITEM_FUNCTION(dithering_item, 0, "Dithering", +MENUITEM_FUNCTION(dithering_item, 0, "Dithering (Jpeg)", set_option_dithering, NULL, NULL, Icon_NOICON); MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON, &grayscale_item, &dithering_item); @@ -220,7 +239,7 @@ static void display_options(void) { rb->do_menu(&display_menu, NULL, NULL, false); } -#endif /* defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) */ +#endif /* HAVE_LCD_COLOR */ static int show_menu(void) /* return 1 to quit */ { @@ -234,19 +253,19 @@ static int show_menu(void) /* return 1 to quit */ #ifdef USE_PLUG_BUF MIID_SHOW_PLAYBACK_MENU, #endif -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) +#ifdef HAVE_LCD_COLOR MIID_DISPLAY_OPTIONS, #endif MIID_QUIT, }; - MENUITEM_STRINGLIST(menu, MENU_TITLE, NULL, + MENUITEM_STRINGLIST(menu, "Image Viewer Menu", NULL, "Return", "Toggle Slideshow Mode", "Change Slideshow Time", #ifdef USE_PLUG_BUF "Show Playback Menu", #endif -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) +#ifdef HAVE_LCD_COLOR "Display Options", #endif "Quit"); @@ -263,7 +282,7 @@ static int show_menu(void) /* return 1 to quit */ case MIID_RETURN: break; case MIID_TOGGLE_SS_MODE: - rb->set_option("Toggle Slideshow", &slideshow_enabled, BOOL, + rb->set_option("Toggle Slideshow", &iv_api.slideshow_enabled, BOOL, slideshow , 2, NULL); break; case MIID_CHANGE_SS_MODE: @@ -274,7 +293,7 @@ static int show_menu(void) /* return 1 to quit */ #ifdef USE_PLUG_BUF case MIID_SHOW_PLAYBACK_MENU: - if (plug_buf) + if (iv_api.plug_buf) { playback_control(NULL); } @@ -284,7 +303,7 @@ static int show_menu(void) /* return 1 to quit */ } break; #endif -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) +#ifdef HAVE_LCD_COLOR case MIID_DISPLAY_OPTIONS: display_options(); break; @@ -296,10 +315,10 @@ static int show_menu(void) /* return 1 to quit */ #ifdef DISK_SPINDOWN /* change ata spindown time based on slideshow time setting */ - immediate_ata_off = false; + iv_api.immediate_ata_off = false; rb->storage_spindown(rb->global_settings->disk_spindown); - if (slideshow_enabled) + if (iv_api.slideshow_enabled) { if(settings.ss_timeout < 10) { @@ -309,7 +328,7 @@ static int show_menu(void) /* return 1 to quit */ else if (!rb->mp3_is_playing()) { /* slideshow times > 10s and not playing: ata_off after load */ - immediate_ata_off = true; + iv_api.immediate_ata_off = true; } } #endif @@ -344,7 +363,7 @@ static int ask_and_get_audio_buffer(const char *filename) switch(button) { case IMGVIEW_ZOOM_IN: - plug_buf = false; + iv_api.plug_buf = false; buf = rb->plugin_get_audio_buffer(&buf_size); /*try again this file, now using the audio buffer */ return PLUGIN_OTHER; @@ -382,15 +401,15 @@ static int ask_and_get_audio_buffer(const char *filename) #endif /* USE_PLUG_BUF */ /* callback updating a progress meter while image decoding */ -void cb_progress(int current, int total) +static void cb_progress(int current, int total) { rb->yield(); /* be nice to the other threads */ #ifndef USEGSLIB /* in slideshow mode, keep gui interference to a minimum */ - const int size = (!running_slideshow ? 8 : 4); + const int size = (!iv_api.running_slideshow ? 8 : 4); #else const int size = 8; - if(!running_slideshow) + if(!iv_api.running_slideshow) #endif { rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], @@ -414,7 +433,8 @@ static void pan_view_right(struct image_info *info) { mylcd_ub_scroll_left(move); /* scroll left */ info->x += move; - draw_image_rect(info, LCD_WIDTH - move, 0, move, info->height-info->y); + imgdec->draw_image_rect(info, LCD_WIDTH - move, 0, + move, info->height-info->y); mylcd_ub_update(); } } @@ -430,7 +450,7 @@ static void pan_view_left(struct image_info *info) { mylcd_ub_scroll_right(move); /* scroll right */ info->x -= move; - draw_image_rect(info, 0, 0, move, info->height-info->y); + imgdec->draw_image_rect(info, 0, 0, move, info->height-info->y); mylcd_ub_update(); } } @@ -446,15 +466,16 @@ static void pan_view_up(struct image_info *info) { mylcd_ub_scroll_down(move); /* scroll down */ info->y -= move; -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) - if (settings.jpeg_dither_mode == DITHER_DIFFUSION) +#ifdef HAVE_LCD_COLOR + if (image_type == IMAGE_JPEG + && settings.jpeg_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, info->y + info->height); } #endif - draw_image_rect(info, 0, 0, info->width-info->x, move); + imgdec->draw_image_rect(info, 0, 0, info->width-info->x, move); mylcd_ub_update(); } } @@ -470,8 +491,9 @@ static void pan_view_down(struct image_info *info) { mylcd_ub_scroll_up(move); /* scroll up */ info->y += move; -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) - if (settings.jpeg_dither_mode == DITHER_DIFFUSION) +#ifdef HAVE_LCD_COLOR + if (image_type == IMAGE_JPEG + && settings.jpeg_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 @@ -484,10 +506,12 @@ static void pan_view_down(struct image_info *info) } #endif - draw_image_rect(info, 0, LCD_HEIGHT - move, info->width-info->x, move); + imgdec->draw_image_rect(info, 0, LCD_HEIGHT - move, + info->width-info->x, move); -#if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) - if (settings.jpeg_dither_mode == DITHER_DIFFUSION) +#ifdef HAVE_LCD_COLOR + if (image_type == IMAGE_JPEG + && settings.jpeg_dither_mode == DITHER_DIFFUSION) { /* Cover the first row drawn with previous image data. */ rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH, @@ -507,12 +531,12 @@ static int scroll_bmp(struct image_info *info) while (true) { - if (slideshow_enabled) + if (iv_api.slideshow_enabled) button = rb->button_get_w_tmo(settings.ss_timeout * HZ); else button = rb->button_get(true); - running_slideshow = false; + iv_api.running_slideshow = false; switch(button) { @@ -543,16 +567,16 @@ static int scroll_bmp(struct image_info *info) break; case BUTTON_NONE: - if (slideshow_enabled && entries > 1) + if (iv_api.slideshow_enabled && entries > 1) { - running_slideshow = true; + iv_api.running_slideshow = true; return change_filename(DIR_NEXT); } break; #ifdef IMGVIEW_SLIDE_SHOW case IMGVIEW_SLIDE_SHOW: - slideshow_enabled = !slideshow_enabled; + iv_api.slideshow_enabled = !iv_api.slideshow_enabled; break; #endif @@ -605,7 +629,7 @@ static int scroll_bmp(struct image_info *info) #ifdef USEGSLIB grey_show(true); /* switch on greyscale overlay */ #else - draw_image_rect(info, 0, 0, + imgdec->draw_image_rect(info, 0, 0, info->width-info->x, info->height-info->y); mylcd_ub_update(); #endif @@ -637,10 +661,10 @@ static int min_downscale(int bufsize) { int downscale = 8; - if (img_mem(8) > bufsize) + if (imgdec->img_mem(8) > bufsize) return 0; /* error, too large, even 1:8 doesn't fit */ - while (downscale > 1 && img_mem(downscale/2) <= bufsize) + while (downscale > 1 && imgdec->img_mem(downscale/2) <= bufsize) downscale /= 2; return downscale; @@ -697,18 +721,39 @@ static int load_and_show(char* filename, struct image_info *info) rb->lcd_clear_display(); + status = get_image_type(filename); + if (image_type != status) /* type of image is changed, load decoder. */ + { + struct loader_info loader_info = { + status, &iv_api, decoder_buf, decoder_buf_size, + }; + image_type = status; + imgdec = load_decoder(&loader_info); + if (imgdec == NULL) + { + /* something is wrong */ + return PLUGIN_ERROR; + } +#ifdef USE_PLUG_BUF + if(iv_api.plug_buf) + { + buf = loader_info.buffer; + buf_size = loader_info.size; + } +#endif + } rb->memset(info, 0, sizeof(*info)); remaining = buf_size; if (rb->button_get(false) == IMGVIEW_MENU) status = PLUGIN_ABORT; else - status = load_image(filename, info, buf, &remaining); + status = imgdec->load_image(filename, info, buf, &remaining); if (status == PLUGIN_OUTOFMEM) { #ifdef USE_PLUG_BUF - if(plug_buf) + if(iv_api.plug_buf) { return ask_and_get_audio_buffer(filename); } @@ -734,13 +779,14 @@ static int load_and_show(char* filename, struct image_info *info) ds_min = min_downscale(remaining); /* check memory constraint */ if (ds_min == 0) { -#if UNSCALED_IS_AVAILABLE - /* Can not resize the image but original one is available, so use it. */ - ds_min = ds_max = 1; -#else - /* not enough memory to decode image. */ + if (imgdec->unscaled_avail) + { + /* Can not resize the image but original one is available, so use it. */ + ds_min = ds_max = 1; + } + else #ifdef USE_PLUG_BUF - if(plug_buf) + if (iv_api.plug_buf) { return ask_and_get_audio_buffer(filename); } @@ -751,7 +797,6 @@ static int load_and_show(char* filename, struct image_info *info) file_pt[curfile] = NULL; return change_filename(direction); } -#endif } else if (ds_max < ds_min) ds_max = ds_min; @@ -762,7 +807,7 @@ static int load_and_show(char* filename, struct image_info *info) do /* loop the image prepare and decoding when zoomed */ { - status = get_image(info, ds); /* decode or fetch from cache */ + status = imgdec->get_image(info, ds); /* decode or fetch from cache */ if (status == PLUGIN_ERROR) { file_pt[curfile] = NULL; @@ -771,14 +816,14 @@ static int load_and_show(char* filename, struct image_info *info) set_view(info, cx, cy); - if(!running_slideshow) + if(!iv_api.running_slideshow) { rb->lcd_putsf(0, 3, "showing %dx%d", info->width, info->height); rb->lcd_update(); } mylcd_ub_clear_display(); - draw_image_rect(info, 0, 0, + imgdec->draw_image_rect(info, 0, 0, info->width-info->x, info->height-info->y); mylcd_ub_update(); @@ -794,18 +839,10 @@ static int load_and_show(char* filename, struct image_info *info) status = scroll_bmp(info); if (status == ZOOM_IN) { -#if UNSCALED_IS_AVAILABLE - if (ds > 1) -#else - if (ds > ds_min) -#endif + if (ds > ds_min || (imgdec->unscaled_avail && ds > 1)) { -#if UNSCALED_IS_AVAILABLE /* if 1/1 is always available, jump ds from ds_min to 1. */ int zoom = (ds == ds_min)? ds_min: 2; -#else - const int zoom = 2; -#endif ds /= zoom; /* reduce downscaling to zoom in */ get_view(info, &cx, &cy); cx *= zoom; /* prepare the position in the new image */ @@ -819,12 +856,8 @@ static int load_and_show(char* filename, struct image_info *info) { if (ds < ds_max) { -#if UNSCALED_IS_AVAILABLE /* if ds is 1 and ds_min is > 1, jump ds to ds_min. */ int zoom = (ds < ds_min)? ds_min: 2; -#else - const int zoom = 2; -#endif ds *= zoom; /* increase downscaling to zoom out */ get_view(info, &cx, &cy); cx /= zoom; /* prepare the position in the new image */ @@ -859,25 +892,24 @@ enum plugin_status plugin_start(const void* parameter) if(!parameter) return PLUGIN_ERROR; + rb->strcpy(np_file, parameter); + if (get_image_type(np_file) == IMAGE_UNKNOWN) + { + rb->splash(HZ*2, "Unsupported file"); + return PLUGIN_ERROR; + } + #ifdef USE_PLUG_BUF buf = rb->plugin_get_buffer(&buf_size); #else + decoder_buf = rb->plugin_get_buffer(&decoder_buf_size); buf = rb->plugin_get_audio_buffer(&buf_size); #endif - rb->strcpy(np_file, parameter); get_pic_list(); if(!entries) return PLUGIN_ERROR; -#ifdef USE_PLUG_BUF - if(!rb->audio_status()) - { - plug_buf = false; - buf = rb->plugin_get_audio_buffer(&buf_size); - } -#endif - #ifdef USEGSLIB if (!grey_init(buf, buf_size, GREY_ON_COP, LCD_WIDTH, LCD_HEIGHT, &greysize)) @@ -889,6 +921,16 @@ enum plugin_status plugin_start(const void* parameter) buf_size -= greysize; #endif +#ifdef USE_PLUG_BUF + decoder_buf = buf; + decoder_buf_size = buf_size; + if(!rb->audio_status()) + { + iv_api.plug_buf = false; + buf = rb->plugin_get_audio_buffer(&buf_size); + } +#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(IMGVIEW_CONFIGFILE, config, @@ -908,6 +950,7 @@ enum plugin_status plugin_start(const void* parameter) { condition = load_and_show(np_file, &image_info); } while (condition >= PLUGIN_OTHER); + release_decoder(); if (rb->memcmp(&settings, &old_settings, sizeof (settings))) { diff --git a/apps/plugins/imageviewer/imageviewer.h b/apps/plugins/imageviewer/imageviewer.h index da2bbfe45c..8e838def08 100644 --- a/apps/plugins/imageviewer/imageviewer.h +++ b/apps/plugins/imageviewer/imageviewer.h @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * user intereface of image viewers (jpeg, png, etc.) + * user intereface of image viewer. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,8 +19,8 @@ * ****************************************************************************/ -#ifndef _IMGVIEW_IMGVIEW_H -#define _IMGVIEW_IMGVIEW_H +#ifndef _IMAGE_VIEWER_H +#define _IMAGE_VIEWER_H #include "plugin.h" @@ -384,6 +384,13 @@ #include +#if defined(USEGSLIB) && defined(IMGDEC) +#undef mylcd_ub_ +#undef myxlcd_ub_ +#define mylcd_ub_(fn) iv->fn +#define myxlcd_ub_(fn) iv->fn +#endif + /* Min memory allowing us to use the plugin buffer * and thus not stopping the music * *Very* rough estimation: @@ -413,7 +420,6 @@ enum { /* Settings. jpeg needs these */ struct imgview_settings { - /* include all settings for varias decoders as using same setting file. */ #ifdef HAVE_LCD_COLOR int jpeg_colour_mode; int jpeg_dither_mode; @@ -421,7 +427,7 @@ struct imgview_settings int ss_timeout; }; -/* structure passed to decoder. */ +/* structure passed to image decoder. */ struct image_info { int x_size, y_size; /* set size of loaded image in load_image(). */ int width, height; /* set size of resized image in get_image(). */ @@ -429,34 +435,78 @@ struct image_info { void *data; /* use freely in decoder. not touched in ui. */ }; -/* callback updating a progress meter while image decoding */ -extern void cb_progress(int current, int total); - -extern struct imgview_settings settings; -extern bool slideshow_enabled; -extern bool running_slideshow; +struct imgdec_api { + const struct imgview_settings *settings; + bool slideshow_enabled; /* run slideshow */ + bool running_slideshow; /* loading image because of slideshw */ #ifdef DISK_SPINDOWN -extern bool immediate_ata_off; + bool immediate_ata_off; /* power down disk after loading */ #endif #ifdef USE_PLUG_BUF -extern bool plug_buf; + bool plug_buf; /* are we using the plugin buffer or the audio buffer? */ #endif + /* callback updating a progress meter while image decoding */ + void (*cb_progress)(int current, int total); + +#ifdef USEGSLIB + void (*gray_bitmap_part)(const unsigned char *src, int src_x, int src_y, + int stride, int x, int y, int width, int height); +#endif +}; + /* functions need to be implemented in each image decoders. */ -/* return true if ext is supported by the decoder. */ -extern bool img_ext(const char *ext); -/* return needed size of buffer to store downscaled image by ds */ -extern int img_mem(int ds); -/* load image from filename. set width and height of info properly. also, set - * buf_size to remaining size of buf after load image. it is used to caluclate - * min downscale. */ -extern int load_image(char *filename, struct image_info *info, +struct image_decoder { + /* if unscaled image can be always displayed when there isn't enough memory + * for resized image. e.g. when using native format to store image. */ + const bool unscaled_avail; + + /* return needed size of buffer to store downscaled image by ds */ + int (*img_mem)(int ds); + /* load image from filename. set width and height of info properly. also, set + * buf_size to remaining size of buf after load image. it is used to caluclate + * min downscale. */ + int (*load_image)(char *filename, struct image_info *info, unsigned char *buf, ssize_t *buf_size); -/* downscale loaded image by ds. note that buf to store reszied image is not - * provided. return PLUGIN_ERROR for error. ui will skip to next image. */ -extern int get_image(struct image_info *info, int ds); -/* draw part of image */ -extern void draw_image_rect(struct image_info *info, + /* downscale loaded image by ds. note that buf to store reszied image is not + * provided. return PLUGIN_ERROR for error. ui will skip to next image. */ + int (*get_image)(struct image_info *info, int ds); + /* draw part of image */ + void (*draw_image_rect)(struct image_info *info, int x, int y, int width, int height); +}; + +#define IMGDEC_API_VERSION (PLUGIN_API_VERSION << 4 | 0) + +/* image decoder header */ +struct imgdec_header { + struct lc_header lc_hdr; /* must be the first */ + const struct image_decoder *decoder; + const struct plugin_api **api; + const struct imgdec_api **img_api; +}; + +#ifdef IMGDEC +extern const struct imgdec_api *iv; +extern const struct image_decoder image_decoder; + +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) +#define IMGDEC_HEADER \ + const struct plugin_api *rb DATA_ATTR; \ + const struct imgdec_api *iv DATA_ATTR; \ + const struct imgdec_header __header \ + __attribute__ ((section (".header")))= { \ + { PLUGIN_MAGIC, TARGET_ID, IMGDEC_API_VERSION, \ + plugin_start_addr, plugin_end_addr }, &image_decoder, &rb, &iv }; +#else /* PLATFORM_HOSTED */ +#define IMGDEC_HEADER \ + const struct plugin_api *rb DATA_ATTR; \ + const struct imgdec_api *iv DATA_ATTR; \ + const struct imgdec_header __header \ + __attribute__((visibility("default"))) = { \ + { PLUGIN_MAGIC, TARGET_ID, IMGDEC_API_VERSION, \ + NULL, NULL }, &image_decoder, &rb, &iv }; +#endif /* CONFIG_PLATFORM */ +#endif -#endif /* _IMGVIEW_IMGVIEW_H */ +#endif /* _IMAGE_VIEWER_H */ diff --git a/apps/plugins/imageviewer/imageviewer.make b/apps/plugins/imageviewer/imageviewer.make index 76af8d24e5..d06bbfd571 100644 --- a/apps/plugins/imageviewer/imageviewer.make +++ b/apps/plugins/imageviewer/imageviewer.make @@ -10,7 +10,50 @@ IMGVSRCDIR := $(APPSDIR)/plugins/imageviewer IMGVBUILDDIR := $(BUILDDIR)/apps/plugins/imageviewer -# include actual viewer's make file +ROCKS += $(IMGVBUILDDIR)/imageviewer.rock + +IMGV_SRC := $(call preprocess, $(IMGVSRCDIR)/SOURCES) +IMGV_OBJ := $(call c2obj, $(IMGV_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(IMGV_SRC) + +$(IMGVBUILDDIR)/imageviewer.rock: $(IMGV_OBJ) + +IMGDECFLAGS = $(PLUGINFLAGS) -DIMGDEC + +# include decoder's make from each subdir IMGVSUBDIRS := $(call preprocess, $(IMGVSRCDIR)/SUBDIRS) $(foreach dir,$(IMGVSUBDIRS),$(eval include $(dir)/$(notdir $(dir)).make)) +IMGDECLDFLAGS = -T$(PLUGINLINK_LDS) -Wl,--gc-sections -Wl,-Map,$(IMGVBUILDDIR)/$*.refmap + +ifndef APP_TYPE + IMGDEC_OUTLDS = $(IMGVBUILDDIR)/%.link + IMGDEC_OVLFLAGS = -T$(IMGVBUILDDIR)/$*.link -Wl,--gc-sections -Wl,-Map,$(IMGVBUILDDIR)/$*.map +else + IMGDEC_OVLFLAGS = $(PLUGINLDFLAGS) +endif + +$(IMGVBUILDDIR)/%.ovl: $(IMGDEC_OUTLDS) + $(call PRINTS,LD $(@F))$(CC) $(IMGDECFLAGS) -o $(IMGVBUILDDIR)/$*.elf \ + $(filter-out $(PLUGIN_CRT0),$(filter %.o, $^)) \ + $(filter %.a, $+) \ + -lgcc $(IMGDEC_OVLFLAGS) +ifdef APP_TYPE + $(SILENT)cp $(IMGVBUILDDIR)/$*.elf $@ +else + $(SILENT)$(OC) -O binary $(IMGVBUILDDIR)/$*.elf $@ +endif + +# rule to create reference map for image decoder +$(IMGVBUILDDIR)/%.refmap: $(APPSDIR)/plugin.h $(IMGVSRCDIR)/imageviewer.h $(PLUGINLINK_LDS) $(PLUGINLIB) $(PLUGINBITMAPLIB) + $(call PRINTS,LD $(@F))$(CC) $(IMGDECFLAGS) -o /dev/null \ + $(filter %.o, $^) \ + $(filter %.a, $+) \ + -lgcc $(IMGDECLDFLAGS) + +$(IMGVBUILDDIR)/%.link: $(PLUGIN_LDS) $(IMGVBUILDDIR)/%.refmap + $(call PRINTS,PP $(@F))$(call preprocess2file,$<,$@,-DIMGVDECODER_OFFSET=$(shell \ + $(TOOLSDIR)/ovl_offset.pl $(IMGVBUILDDIR)/$*.refmap)) + diff --git a/apps/plugins/imageviewer/jpeg/SOURCES b/apps/plugins/imageviewer/jpeg/SOURCES index 8e80722a5a..c3524001e2 100644 --- a/apps/plugins/imageviewer/jpeg/SOURCES +++ b/apps/plugins/imageviewer/jpeg/SOURCES @@ -1,4 +1,3 @@ -jpeg_ui.c jpeg.c jpeg_decoder.c #ifdef HAVE_LCD_COLOR diff --git a/apps/plugins/imageviewer/jpeg/jpeg.c b/apps/plugins/imageviewer/jpeg/jpeg.c index 5f69cc7f51..511a7054e1 100644 --- a/apps/plugins/imageviewer/jpeg/jpeg.c +++ b/apps/plugins/imageviewer/jpeg/jpeg.c @@ -69,20 +69,8 @@ static struct jpeg jpg; /* too large for stack */ /************************* Implementation ***************************/ -bool img_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; -} - -void draw_image_rect(struct image_info *info, - int x, int y, int width, int height) +static void draw_image_rect(struct image_info *info, + int x, int y, int width, int height) { struct t_disp* pdisp = (struct t_disp*)info->data; #ifdef HAVE_LCD_COLOR @@ -92,7 +80,7 @@ void draw_image_rect(struct image_info *info, x + MAX(0, (LCD_WIDTH - info->width) / 2), y + MAX(0, (LCD_HEIGHT - info->height) / 2), width, height, - settings.jpeg_colour_mode, settings.jpeg_dither_mode); + iv->settings->jpeg_colour_mode, iv->settings->jpeg_dither_mode); #else mylcd_ub_gray_bitmap_part( pdisp->bitmap[0], info->x + x, info->y + y, pdisp->stride, @@ -102,7 +90,7 @@ void draw_image_rect(struct image_info *info, #endif } -int img_mem(int ds) +static int img_mem(int ds) { int size; struct jpeg *p_jpg = &jpg; @@ -121,8 +109,8 @@ int img_mem(int ds) return size; } -int load_image(char *filename, struct image_info *info, - unsigned char *buf, ssize_t *buf_size) +static int load_image(char *filename, struct image_info *info, + unsigned char *buf, ssize_t *buf_size) { int fd; int filesize; @@ -154,7 +142,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_OUTOFMEM; } - if(!running_slideshow) + if(!iv->running_slideshow) { rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1); rb->lcd_putsf(0, 1, "loading %d bytes", filesize); @@ -164,13 +152,13 @@ int load_image(char *filename, struct image_info *info, rb->read(fd, buf_jpeg, filesize); rb->close(fd); - if(!running_slideshow) + if(!iv->running_slideshow) { rb->lcd_puts(0, 2, "decoding markers"); rb->lcd_update(); } #ifdef DISK_SPINDOWN - else if(immediate_ata_off) + else if(iv->immediate_ata_off) { /* running slideshow and time is long enough: power down disk */ rb->storage_sleep(); @@ -190,7 +178,7 @@ int load_image(char *filename, struct image_info *info, default_huff_tbl(p_jpg); /* use default */ build_lut(p_jpg); /* derive Huffman and other lookup-tables */ - if(!running_slideshow) + if(!iv->running_slideshow) { rb->lcd_putsf(0, 2, "image %dx%d", p_jpg->x_size, p_jpg->y_size); rb->lcd_update(); @@ -202,7 +190,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_OK; } -int get_image(struct image_info *info, int ds) +static int get_image(struct image_info *info, int ds) { int w, h; /* used to center output */ int size; /* decompressed image size */ @@ -262,7 +250,7 @@ int get_image(struct image_info *info, int ds) buf_images += size; buf_images_size -= size; - if(!running_slideshow) + if(!iv->running_slideshow) { rb->lcd_putsf(0, 3, "decoding %d*%d", info->width, info->height); rb->lcd_update(); @@ -275,10 +263,10 @@ int get_image(struct image_info *info, int ds) time = *rb->current_tick; #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); - status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progress); + status = jpeg_decode(p_jpg, p_disp->bitmap, ds, iv->cb_progress); rb->cpu_boost(false); #else - status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progress); + status = jpeg_decode(p_jpg, p_disp->bitmap, ds, iv->cb_progress); #endif if (status) { @@ -287,7 +275,7 @@ int get_image(struct image_info *info, int ds) } time = *rb->current_tick - time; - if(!running_slideshow) + if(!iv->running_slideshow) { rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ); rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */ @@ -297,3 +285,13 @@ int get_image(struct image_info *info, int ds) return PLUGIN_OK; } + +const struct image_decoder image_decoder = { + false, + img_mem, + load_image, + get_image, + draw_image_rect, +}; + +IMGDEC_HEADER diff --git a/apps/plugins/imageviewer/jpeg/jpeg.make b/apps/plugins/imageviewer/jpeg/jpeg.make index caf37fc74c..dd7addca56 100644 --- a/apps/plugins/imageviewer/jpeg/jpeg.make +++ b/apps/plugins/imageviewer/jpeg/jpeg.make @@ -10,12 +10,18 @@ JPEGSRCDIR := $(IMGVSRCDIR)/jpeg JPEGBUILDDIR := $(IMGVBUILDDIR)/jpeg -ROCKS += $(JPEGBUILDDIR)/jpeg.rock - JPEG_SRC := $(call preprocess, $(JPEGSRCDIR)/SOURCES) JPEG_OBJ := $(call c2obj, $(JPEG_SRC)) -# add source files to OTHER_SRC to get automatic dependencies OTHER_SRC += $(JPEG_SRC) -$(JPEGBUILDDIR)/jpeg.rock: $(JPEG_OBJ) +ROCKS += $(JPEGBUILDDIR)/jpeg.ovl + +$(JPEGBUILDDIR)/jpeg.refmap: $(JPEG_OBJ) +$(JPEGBUILDDIR)/jpeg.link: $(PLUGIN_LDS) $(JPEGBUILDDIR)/jpeg.refmap +$(JPEGBUILDDIR)/jpeg.ovl: $(JPEG_OBJ) + +# special pattern rule for compiling image decoder with extra flags +$(JPEGBUILDDIR)/%.o: $(JPEGSRCDIR)/%.c $(JPEGSRCDIR)/jpeg.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(IMGDECFLAGS) -c $< -o $@ diff --git a/apps/plugins/imageviewer/jpeg/jpeg_ui.c b/apps/plugins/imageviewer/jpeg/jpeg_ui.c deleted file mode 100644 index e7f57c699f..0000000000 --- a/apps/plugins/imageviewer/jpeg/jpeg_ui.c +++ /dev/null @@ -1,5 +0,0 @@ -#define JPEG_VIEWER -#define MENU_TITLE "Jpeg Menu" -#define UNSCALED_IS_AVAILABLE 0 - -#include "../imageviewer.c" diff --git a/apps/plugins/imageviewer/png/SOURCES b/apps/plugins/imageviewer/png/SOURCES index 8c278b02b9..978150db78 100644 --- a/apps/plugins/imageviewer/png/SOURCES +++ b/apps/plugins/imageviewer/png/SOURCES @@ -3,4 +3,3 @@ tinflate.c tinfzlib.c png_decoder.c png.c -png_ui.c diff --git a/apps/plugins/imageviewer/png/png.c b/apps/plugins/imageviewer/png/png.c index 956cad37d8..10404b7c30 100644 --- a/apps/plugins/imageviewer/png/png.c +++ b/apps/plugins/imageviewer/png/png.c @@ -50,18 +50,13 @@ static unsigned char *disp_buf; #define resize_bitmap grey_resize_bitmap #endif -bool img_ext(const char *ext) -{ - if (!ext) - return false; - if (!rb->strcasecmp(ext,".png")) - return true; - else - return false; -} +#if defined(USEGSLIB) && (CONFIG_PLATFORM & PLATFORM_HOSTED) +/* hack: fix error "undefined reference to `_grey_info'". */ +GREY_INFO_STRUCT +#endif /* USEGSLIB */ -void draw_image_rect(struct image_info *info, - int x, int y, int width, int height) +static void draw_image_rect(struct image_info *info, + int x, int y, int width, int height) { unsigned char **pdisp = (unsigned char **)info->data; @@ -80,7 +75,7 @@ void draw_image_rect(struct image_info *info, #endif } -int img_mem(int ds) +static int img_mem(int ds) { LodePNG_Decoder *p_decoder = &decoder; @@ -93,8 +88,8 @@ int img_mem(int ds) #endif } -int load_image(char *filename, struct image_info *info, - unsigned char *buf, ssize_t *buf_size) +static int load_image(char *filename, struct image_info *info, + unsigned char *buf, ssize_t *buf_size) { int fd; long time = 0; /* measured ticks */ @@ -122,7 +117,7 @@ int load_image(char *filename, struct image_info *info, DEBUGF("reading file '%s'\n", filename); - if (!running_slideshow) { + if (!iv->running_slideshow) { rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1); rb->lcd_update(); } @@ -132,7 +127,7 @@ int load_image(char *filename, struct image_info *info, rb->close(fd); } else { - if (!running_slideshow) { + if (!iv->running_slideshow) { rb->lcd_putsf(0, 1, "loading %zu bytes", file_size); rb->lcd_update(); } @@ -142,12 +137,12 @@ int load_image(char *filename, struct image_info *info, rb->read(fd, image, file_size); rb->close(fd); - if (!running_slideshow) { + if (!iv->running_slideshow) { rb->lcd_puts(0, 2, "decoding image"); rb->lcd_update(); } #ifdef DISK_SPINDOWN - else if (immediate_ata_off) { + else if (iv->immediate_ata_off) { /* running slideshow and time is long enough: power down disk */ rb->storage_sleep(); } @@ -167,7 +162,7 @@ int load_image(char *filename, struct image_info *info, if (!p_decoder->error) { - if (!running_slideshow) { + if (!iv->running_slideshow) { rb->lcd_putsf(0, 2, "image %dx%d", p_decoder->infoPng.width, p_decoder->infoPng.height); @@ -181,16 +176,16 @@ int load_image(char *filename, struct image_info *info, time = *rb->current_tick; #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); - LodePNG_decode(p_decoder, image, file_size, cb_progress); + LodePNG_decode(p_decoder, image, file_size, iv->cb_progress); rb->cpu_boost(false); #else - LodePNG_decode(p_decoder, image, file_size, cb_progress); + LodePNG_decode(p_decoder, image, file_size, iv->cb_progress); #endif /*HAVE_ADJUSTABLE_CPU_FREQ*/ time = *rb->current_tick - time; } } - if (!running_slideshow && !p_decoder->error) + if (!iv->running_slideshow && !p_decoder->error) { rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ); rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */ @@ -200,7 +195,7 @@ int load_image(char *filename, struct image_info *info, if (p_decoder->error) { #ifdef USE_PLUG_BUF - if (plug_buf && (p_decoder->error == FILE_TOO_LARGE || + if (iv->plug_buf && (p_decoder->error == FILE_TOO_LARGE || p_decoder->error == OUT_OF_MEMORY || p_decoder->error == TINF_DATA_ERROR)) return PLUGIN_OUTOFMEM; @@ -244,7 +239,7 @@ int load_image(char *filename, struct image_info *info, return PLUGIN_OK; } -int get_image(struct image_info *info, int ds) +static int get_image(struct image_info *info, int ds) { unsigned char **p_disp = &disp[ds]; /* short cut */ LodePNG_Decoder *p_decoder = &decoder; @@ -261,7 +256,7 @@ int get_image(struct image_info *info, int ds) /* assign image buffer */ if (ds > 1) { - if (!running_slideshow) + if (!iv->running_slideshow) { rb->lcd_putsf(0, 3, "resizing %d*%d", info->width, info->height); rb->lcd_update(); @@ -303,3 +298,13 @@ int get_image(struct image_info *info, int ds) return PLUGIN_OK; } + +const struct image_decoder image_decoder = { + true, + img_mem, + load_image, + get_image, + draw_image_rect, +}; + +IMGDEC_HEADER diff --git a/apps/plugins/imageviewer/png/png.make b/apps/plugins/imageviewer/png/png.make index 0a7106d2a9..dee89acb71 100644 --- a/apps/plugins/imageviewer/png/png.make +++ b/apps/plugins/imageviewer/png/png.make @@ -10,18 +10,19 @@ PNGSRCDIR := $(IMGVSRCDIR)/png PNGBUILDDIR := $(IMGVBUILDDIR)/png -ROCKS += $(PNGBUILDDIR)/png.rock - PNG_SRC := $(call preprocess, $(PNGSRCDIR)/SOURCES) PNG_OBJ := $(call c2obj, $(PNG_SRC)) -# add source files to OTHER_SRC to get automatic dependencies OTHER_SRC += $(PNG_SRC) -# Use -O3 for png plugin : it gives a bigger file but very good performances -PNGFLAGS = $(PLUGINFLAGS) -Os +ROCKS += $(PNGBUILDDIR)/png.ovl -$(PNGBUILDDIR)/png.rock: $(PNG_OBJ) +$(PNGBUILDDIR)/png.refmap: $(PNG_OBJ) +$(PNGBUILDDIR)/png.link: $(PNG_OBJ) $(PNGBUILDDIR)/png.refmap +$(PNGBUILDDIR)/png.ovl: $(PNG_OBJ) + +# Use -O3 for png plugin : it gives a bigger file but very good performances +PNGFLAGS = $(IMGDECFLAGS) -Os # Compile PNG plugin with extra flags (adapted from ZXBox) $(PNGBUILDDIR)/%.o: $(PNGSRCDIR)/%.c $(PNGSRCDIR)/png.make diff --git a/apps/plugins/imageviewer/png/png_ui.c b/apps/plugins/imageviewer/png/png_ui.c deleted file mode 100644 index 5dbf526ba1..0000000000 --- a/apps/plugins/imageviewer/png/png_ui.c +++ /dev/null @@ -1,5 +0,0 @@ -#define PNG_VIEWER -#define MENU_TITLE "Png Menu" -#define UNSCALED_IS_AVAILABLE 1 - -#include "../imageviewer.c" diff --git a/apps/plugins/plugin.lds b/apps/plugins/plugin.lds index 2f11bd1235..f4bd64df35 100644 --- a/apps/plugins/plugin.lds +++ b/apps/plugins/plugin.lds @@ -181,6 +181,9 @@ OUTPUT_FORMAT(elf32-littlemips) #elif defined OVERLAY_OFFSET #define THIS_LENGTH (DRAMSIZE - OVERLAY_OFFSET) #define THIS_ORIGIN (DRAMORIG + OVERLAY_OFFSET) +#elif defined IMGVDECODER_OFFSET +#define THIS_LENGTH (PLUGIN_LENGTH - IMGVDECODER_OFFSET) +#define THIS_ORIGIN (PLUGIN_ORIGIN + IMGVDECODER_OFFSET) #else /* plugin */ #define THIS_LENGTH PLUGIN_LENGTH #define THIS_ORIGIN PLUGIN_ORIGIN diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index aa8b51b478..e0ebdbb496 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -13,11 +13,11 @@ ch8,viewers/chip8,0 txt,viewers/text_viewer,1 nfo,viewers/text_viewer,1 txt,apps/text_editor,2 -bmp,viewers/bmp,2 -jpg,viewers/jpeg,2 -jpe,viewers/jpeg,2 -jpeg,viewers/jpeg,2 -png,viewers/png,2 +bmp,viewers/imageviewer,2 +jpg,viewers/imageviewer,2 +jpe,viewers/imageviewer,2 +jpeg,viewers/imageviewer,2 +png,viewers/imageviewer,2 ucl,viewers/rockbox_flash,3 rvf,viewers/video,4 mp3,viewers/vbrfix,5 diff --git a/manual/plugins/bmpviewer.tex b/manual/plugins/bmpviewer.tex deleted file mode 100755 index 44fd5e98f8..0000000000 --- a/manual/plugins/bmpviewer.tex +++ /dev/null @@ -1,115 +0,0 @@ -% $Id$ % -\subsection{BMP viewer} -This plugin opens \fname{.bmp} files from the \setting{File Browser} to display them\nopt{lcd_color}{ using Rockbox's greyscale library}. -\opt{swcodec}{ -\par -\note{ -When an audio file is playing the size of the image is limited as -the decoding process needs to share memory with audio tracks. To be able to -view a bigger file you may need to stop playback.} -} -\nopt{large_plugin_buffer}{% -\note{This plugin will cause playback to stop.}% -}% - -\begin{btnmap} - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,PBELL_VIBE500_PAD} - {\ButtonUp\ / \ButtonDown}% - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu\ / \ButtonPlay}% - \opt{IRIVER_H10_PAD}{\ButtonScrollUp\ / \ButtonScrollDown} % - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IRIVER_H10_PAD,PBELL_VIBE500_PAD} - {/ \ButtonLeft\ / \ButtonRight} - \opt{COWON_D2_PAD}{} - \opt{HAVEREMOTEKEYMAP}{& } - & Move around in zoomed in image\\ - \opt{RECORDER_PAD}{\ButtonPlay} - \opt{ONDIO_PAD,COWON_D2_PAD}{\ButtonMenu} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD,SANSA_E200_PAD% - ,SANSA_FUZE_PAD,SANSA_C200_PAD,MROBE100_PAD}{\ButtonSelect} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollFwd} - \opt{IRIVER_H10_PAD}{\ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonUp} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom in\\ - \opt{RECORDER_PAD}{\ButtonOn} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonDown} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollBack} - \opt{IAUDIO_X5_PAD,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{Long \ButtonSelect} - \opt{IRIVER_H10_PAD}{Long \ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown} - \opt{MROBE100_PAD}{\ButtonPlay} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonDown} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom out\\ - \opt{RECORDER_PAD}{\ButtonFThree} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonRight} - \opt{IRIVER_H100_PAD}{\ButtonOn} - \opt{IRIVER_H300_PAD}{\ButtonRec} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonRight} - \opt{IAUDIO_X5_PAD}{\ButtonPlay} - \opt{IRIVER_H10_PAD}{\ButtonFF} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollFwd} - \opt{SANSA_C200_PAD}{\ButtonVolUp} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonRight} - \opt{GIGABEAT_S_PAD}{\ButtonNext} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonRight} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonRight} - \opt{HAVEREMOTEKEYMAP}{& } - & Next bmp in directory\\ - \opt{RECORDER_PAD}{\ButtonFTwo} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonLeft} - \opt{IRIVER_H100_PAD,IAUDIO_X5_PAD}{\ButtonRec} - \opt{IRIVER_H300_PAD}{\ButtonOn} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonLeft} - \opt{IRIVER_H10_PAD}{\ButtonRew} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollBack} - \opt{SANSA_C200_PAD}{\ButtonVolDown} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonLeft} - \opt{GIGABEAT_S_PAD}{\ButtonPrev} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonLeft} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonLeft} - \opt{HAVEREMOTEKEYMAP}{& } - & Previous bmp in directory\\ - \opt{SANSA_E200_PAD,SANSA_C200_PAD}{%currently only defined for the sansa pads - \ButtonRec - \opt{HAVEREMOTEKEYMAP}{& } - & Toggle slide show mode\\ - } - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonMenu} - \opt{IAUDIO_X5_PAD,IRIVER_H10_PAD,SANSA_E200_PAD,SANSA_C200_PAD}{\ButtonPower} - \opt{SANSA_FUZE_PAD}{Long \ButtonHome} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{\ButtonMenu} - \opt{COWON_D2_PAD}{\ButtonPower} - \opt{HAVEREMOTEKEYMAP}{& - \opt{IRIVER_RC_H100_PAD}{\ButtonRCStop} - } - & Show menu / Abort \\ - \opt{IPOD_4G_PAD,IPOD_3G_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{ - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonPlay} - \opt{GIGABEAT_PAD,MROBE100_PAD}{\ButtonPower} - \opt{GIGABEAT_S_PAD}{\ButtonBack} - \opt{PBELL_VIBE500_PAD}{\ButtonCancel} - \opt{HAVEREMOTEKEYMAP}{& } - & Quit the viewer \\ - } -\end{btnmap} - -The menu has the following entries. -\begin{description} -\item[Return.] Returns you to the image -\item[Toggle Slideshow Mode.] Enables or disables the slideshow mode. -\item[Change Slideshow Timeout.] You can set the timeout for the slideshow - between 1 second and 20 seconds. -\opt{large_plugin_buffer}{ -\item[Show Playback Menu.] From the playback menu you can control the -playback of the currently loaded playlist and change the volume of your \dap. -} -\item[Quit.] Quits the viewer and returns to the \setting{File Browser}. -\end{description} diff --git a/manual/plugins/imageviewer.tex b/manual/plugins/imageviewer.tex new file mode 100644 index 0000000000..28ef62e3a6 --- /dev/null +++ b/manual/plugins/imageviewer.tex @@ -0,0 +1,142 @@ +% $Id$ % +\subsection{Image Viewer} +This plugin opens image files from the \setting{File Browser} to display them\nopt{lcd_color}{ using Rockbox's greyscale library}. Supported formats are as follows. + +\begin{table} + \begin{rbtabular}{.60\textwidth}{llX}% + {\textbf{Format}& \textbf{File-extension(s)}}% + {}{} + BMP & \fname{.bmp} \\ + JPEG & \fname{.jpg, .jpe, .jpeg} \\ + PNG & \fname{.png} \\ + \end{rbtabular} +\end{table} + +\opt{large_plugin_buffer}{ +\par +\note{ +When an audio file is playing the size of the image is limited as +the decoding process needs to share memory with audio tracks. To be able to +view a bigger file you may need to stop playback.} +} +\nopt{large_plugin_buffer}{% +\note{This plugin will cause playback to stop.}% +}% + +\begin{btnmap} + \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% + ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% + ,MROBE100_PAD,PBELL_VIBE500_PAD} + {\ButtonUp\ / \ButtonDown}% + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu\ / \ButtonPlay}% + \opt{IRIVER_H10_PAD}{\ButtonScrollUp\ / \ButtonScrollDown} % + \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% + ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% + ,MROBE100_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IRIVER_H10_PAD,PBELL_VIBE500_PAD} + {/ \ButtonLeft\ / \ButtonRight} + \opt{touchscreen}{\TouchTopMiddle{} / \TouchBottomMiddle{}/ \TouchMidLeft{} / \TouchMidRight} + \opt{HAVEREMOTEKEYMAP}{& } + & Move around in zoomed in image\\ + \opt{RECORDER_PAD}{\ButtonPlay} + \opt{ONDIO_PAD}{\ButtonMenu} + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD,SANSA_E200_PAD% + ,SANSA_FUZE_PAD,SANSA_C200_PAD,MROBE100_PAD}{\ButtonSelect} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollFwd} + \opt{IRIVER_H10_PAD}{\ButtonPlay} + \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp} + \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonUp} + \opt{touchscreen}{\TouchTopRight} + \opt{HAVEREMOTEKEYMAP}{& } + & Zoom in\\ + \opt{RECORDER_PAD}{\ButtonOn} + \opt{ONDIO_PAD}{\ButtonMenu+\ButtonDown} + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollBack} + \opt{IAUDIO_X5_PAD,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{Long \ButtonSelect} + \opt{IRIVER_H10_PAD}{Long \ButtonPlay} + \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown} + \opt{MROBE100_PAD}{\ButtonPlay} + \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonDown} + \opt{touchscreen}{\TouchTopLeft} + \opt{HAVEREMOTEKEYMAP}{& } + & Zoom out\\ + \opt{RECORDER_PAD}{\ButtonFThree} + \opt{ONDIO_PAD}{\ButtonMenu+\ButtonRight} + \opt{IRIVER_H100_PAD}{\ButtonOn} + \opt{IRIVER_H300_PAD}{\ButtonRec} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonRight} + \opt{IAUDIO_X5_PAD}{\ButtonPlay} + \opt{IRIVER_H10_PAD}{\ButtonFF} + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollFwd} + \opt{SANSA_C200_PAD}{\ButtonVolUp} + \opt{GIGABEAT_PAD}{\ButtonA+\ButtonRight} + \opt{GIGABEAT_S_PAD}{\ButtonNext} + \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonRight} + \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonRight} + \opt{touchscreen}{\TouchBottomRight} + \opt{HAVEREMOTEKEYMAP}{& } + & Next image in directory\\ + \opt{RECORDER_PAD}{\ButtonFTwo} + \opt{ONDIO_PAD}{\ButtonMenu+\ButtonLeft} + \opt{IRIVER_H100_PAD,IAUDIO_X5_PAD}{\ButtonRec} + \opt{IRIVER_H300_PAD}{\ButtonOn} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonLeft} + \opt{IRIVER_H10_PAD}{\ButtonRew} + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollBack} + \opt{SANSA_C200_PAD}{\ButtonVolDown} + \opt{GIGABEAT_PAD}{\ButtonA+\ButtonLeft} + \opt{GIGABEAT_S_PAD}{\ButtonPrev} + \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonLeft} + \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonLeft} + \opt{touchscreen}{\TouchBottomLeft} + \opt{HAVEREMOTEKEYMAP}{& } + & Previous image in directory\\ + \opt{SANSA_E200_PAD,SANSA_C200_PAD}{%currently only defined for the sansa pads + \ButtonRec + \opt{HAVEREMOTEKEYMAP}{& } + & Toggle slide show mode\\ + } + \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonMenu} + \opt{IAUDIO_X5_PAD,IRIVER_H10_PAD,SANSA_E200_PAD,SANSA_C200_PAD}{\ButtonPower} + \opt{SANSA_FUZE_PAD}{Long \ButtonHome} + \opt{GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{\ButtonMenu} + \opt{touchscreen}{\TouchCenter} + \opt{HAVEREMOTEKEYMAP}{& + \opt{IRIVER_RC_H100_PAD}{\ButtonRCStop} + } + & Show menu / Abort \\ + \opt{IPOD_4G_PAD,IPOD_3G_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{ + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonPlay} + \opt{GIGABEAT_PAD,MROBE100_PAD}{\ButtonPower} + \opt{GIGABEAT_S_PAD}{\ButtonBack} + \opt{PBELL_VIBE500_PAD}{\ButtonCancel} + \opt{HAVEREMOTEKEYMAP}{& } + & Quit the viewer \\ + } +\end{btnmap} + +The menu has the following entries. +\begin{description} +\item[Return.] Returns you to the image +\item[Toggle Slideshow Mode.] Enables or disables the slideshow mode. +\item[Change Slideshow Timeout.] You can set the timeout for the slideshow + between 1 second and 20 seconds. +\opt{large_plugin_buffer}{ +\item[Show Playback Menu.] From the playback menu you can control the +playback of the currently loaded playlist and change the volume of your \dap. +} +\opt{lcd_color}{ +\item[Display Options.] From this menu you can force the viewer to render the +image in greyscale using the \setting{Greyscale} option or set the method of +dithering used in the \setting{Dithering} submenu. These settings only take affect +for JPEG images. +} +\item[Quit.] Quits the viewer and returns to the \setting{File Browser}. +\end{description} + +\note{ +Progressive scan and other unusual JPEG files are not supported, and will +result in various ``unsupported xx'' messages. Processing could also fail if the +image is too big to decode which will be explained by a respective message. +} diff --git a/manual/plugins/jpegviewer.tex b/manual/plugins/jpegviewer.tex deleted file mode 100644 index 82e3881427..0000000000 --- a/manual/plugins/jpegviewer.tex +++ /dev/null @@ -1,126 +0,0 @@ -% $Id$ % -\subsection{JPEG viewer} -This plugin opens \fname{.jpeg} files from the \setting{File Browser} to display them\nopt{lcd_color}{ using Rockbox's greyscale library}. -\opt{swcodec}{ -\par -\note{ -When an audio file is playing the size of the image is limited as -the decoding process needs to share memory with audio tracks. To be able to -view a bigger file you may need to stop playback.} -} -\nopt{large_plugin_buffer}{% -\note{This plugin will cause playback to stop.}% -}% - -\begin{btnmap} - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,PBELL_VIBE500_PAD} - {\ButtonUp\ / \ButtonDown}% - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu\ / \ButtonPlay}% - \opt{IRIVER_H10_PAD}{\ButtonScrollUp\ / \ButtonScrollDown} % - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IRIVER_H10_PAD,PBELL_VIBE500_PAD} - {/ \ButtonLeft\ / \ButtonRight} - \opt{COWON_D2_PAD}{} - \opt{HAVEREMOTEKEYMAP}{& } - & Move around in zoomed in image\\ - \opt{RECORDER_PAD}{\ButtonPlay} - \opt{ONDIO_PAD,COWON_D2_PAD}{\ButtonMenu} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD,SANSA_E200_PAD% - ,SANSA_FUZE_PAD,SANSA_C200_PAD,MROBE100_PAD}{\ButtonSelect} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollFwd} - \opt{IRIVER_H10_PAD}{\ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonUp} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom in\\ - \opt{RECORDER_PAD}{\ButtonOn} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonDown} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollBack} - \opt{IAUDIO_X5_PAD,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{Long \ButtonSelect} - \opt{IRIVER_H10_PAD}{Long \ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown} - \opt{MROBE100_PAD}{\ButtonPlay} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonDown} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom out\\ - \opt{RECORDER_PAD}{\ButtonFThree} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonRight} - \opt{IRIVER_H100_PAD}{\ButtonOn} - \opt{IRIVER_H300_PAD}{\ButtonRec} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonRight} - \opt{IAUDIO_X5_PAD}{\ButtonPlay} - \opt{IRIVER_H10_PAD}{\ButtonFF} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollFwd} - \opt{SANSA_C200_PAD}{\ButtonVolUp} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonRight} - \opt{GIGABEAT_S_PAD}{\ButtonNext} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonRight} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonRight} - \opt{HAVEREMOTEKEYMAP}{& } - & Next jpeg in directory\\ - \opt{RECORDER_PAD}{\ButtonFTwo} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonLeft} - \opt{IRIVER_H100_PAD,IAUDIO_X5_PAD}{\ButtonRec} - \opt{IRIVER_H300_PAD}{\ButtonOn} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonLeft} - \opt{IRIVER_H10_PAD}{\ButtonRew} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollBack} - \opt{SANSA_C200_PAD}{\ButtonVolDown} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonLeft} - \opt{GIGABEAT_S_PAD}{\ButtonPrev} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonLeft} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonLeft} - \opt{HAVEREMOTEKEYMAP}{& } - & Previous jpeg in directory\\ - \opt{SANSA_E200_PAD,SANSA_C200_PAD}{%currently only defined for the sansa pads - \ButtonRec - \opt{HAVEREMOTEKEYMAP}{& } - & Toggle slide show mode\\ - } - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonMenu} - \opt{IAUDIO_X5_PAD,IRIVER_H10_PAD,SANSA_E200_PAD,SANSA_C200_PAD}{\ButtonPower} - \opt{SANSA_FUZE_PAD}{Long \ButtonHome} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{\ButtonMenu} - \opt{COWON_D2_PAD}{\ButtonPower} - \opt{HAVEREMOTEKEYMAP}{& - \opt{IRIVER_RC_H100_PAD}{\ButtonRCStop} - } - & Show menu / Abort \\ - \opt{IPOD_4G_PAD,IPOD_3G_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{ - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonPlay} - \opt{GIGABEAT_PAD,MROBE100_PAD}{\ButtonPower} - \opt{GIGABEAT_S_PAD}{\ButtonBack} - \opt{PBELL_VIBE500_PAD}{\ButtonCancel} - \opt{HAVEREMOTEKEYMAP}{& } - & Quit the viewer \\ - } -\end{btnmap} - -The menu has the following entries. -\begin{description} -\item[Return.] Returns you to the image -\item[Toggle Slideshow Mode.] Enables or disables the slideshow mode. -\item[Change Slideshow Timeout.] You can set the timeout for the slideshow - between 1 second and 20 seconds. -\opt{large_plugin_buffer}{ -\item[Show Playback Menu.] From the playback menu you can control the -playback of the currently loaded playlist and change the volume of your \dap. -} -\opt{lcd_color}{ -\item[Display Options.] From this menu you can force the viewer to render the -image in greyscale using the \setting{Greyscale} option or set the method of -dithering used in the \setting{Dithering} submenu. -} -\item[Quit.] Quits the viewer and returns to the \setting{File Browser}. -\end{description} - -\note{ -Progressive scan and other unusual JPEG files are not supported, and will -result in various ``unsupported xx'' messages. Processing could also fail if the -image is too big to decode which will be explained by a respective message. -} diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index a40c81d630..b847c15b51 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -137,11 +137,10 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} \begin{rbtabular}{.92\textwidth}{Xlc}% {\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}% {}{} - BMP Viewer & \fname{.bmp} & \\ Shortcuts & \fname{.link} & \\ Chip-8 Emulator & \fname{.ch8} & \\ Frotz & \fname{.z1 - .z8} & \\ - JPEG Viewer & \fname{.jpg, .jpeg} & \\ + Image Viewer & \fname{.bmp, .jpg, .jpeg, .png} & \\ Lua scripting language& \fname{.lua} & \\ \opt{swcodec}{\nopt{lowmem}{ Midiplay & \fname{.mid, .midi} & \\ @@ -152,9 +151,6 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} Movie Player & \fname{.rvf} & \\ } } - \opt{lcd_bitmap}{ - PNG viewer & \fname{.png} & \\ - } \opt{lcd_color}{ PPM viewer & \fname{.ppm} & \\ } @@ -177,13 +173,11 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} {\input{plugins/shortcuts.tex}} -\opt{lcd_bitmap}{\input{plugins/bmpviewer.tex}} - \opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}} \opt{lcd_bitmap}{\input{plugins/frotz.tex}} -\opt{lcd_bitmap}{\input{plugins/jpegviewer.tex}} +\opt{lcd_bitmap}{\input{plugins/imageviewer.tex}} \opt{large_plugin_buffer}{\input{plugins/lua.tex}} @@ -193,8 +187,6 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} \opt{lcd_bitmap}{\opt{swcodec}{\nopt{lowmem}{\input{plugins/mpegplayer.tex}}}} -\opt{lcd_bitmap}{\input{plugins/pngviewer.tex}} - \opt{lcd_color}{\input{plugins/ppmviewer.tex}} \opt{archosrecorder,archosfmrecorder,ondio}{\input{plugins/rockbox_flash.tex}} diff --git a/manual/plugins/pngviewer.tex b/manual/plugins/pngviewer.tex deleted file mode 100644 index 3dfb6d047a..0000000000 --- a/manual/plugins/pngviewer.tex +++ /dev/null @@ -1,113 +0,0 @@ -% $Id$ % -\subsection{PNG viewer} -This plugin opens \fname{.png} files from the \setting{File Browser} to -display them. -\opt{swcodec}{ - \note{When an audio file is playing the size of the image is limited as - the decoding process needs to share memory with audio tracks. To be able to - view a bigger file you may need to stop playback.\\} -} -\nopt{large_plugin_buffer}{% -\note{This plugin will cause playback to stop.}% -}% - -\begin{btnmap} - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,PBELL_VIBE500_PAD} - {\ButtonUp\ / \ButtonDown}% - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu\ / \ButtonPlay}% - \opt{IRIVER_H10_PAD}{\ButtonScrollUp\ / \ButtonScrollDown} % - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% - ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD% - ,MROBE100_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IRIVER_H10_PAD,PBELL_VIBE500_PAD} - {/ \ButtonLeft\ / \ButtonRight} - \opt{HAVEREMOTEKEYMAP}{& } - & Move around in zoomed in image\\ - \opt{RECORDER_PAD}{\ButtonPlay} - \opt{ONDIO_PAD}{\ButtonMenu} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD,SANSA_E200_PAD% - ,SANSA_FUZE_PAD,SANSA_C200_PAD,MROBE100_PAD}{\ButtonSelect} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollFwd} - \opt{IRIVER_H10_PAD}{\ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonUp} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom in\\ - \opt{RECORDER_PAD}{\ButtonOn} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonDown} - \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonScrollBack} - \opt{IAUDIO_X5_PAD,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD}{Long \ButtonSelect} - \opt{IRIVER_H10_PAD}{Long \ButtonPlay} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolDown} - \opt{MROBE100_PAD}{\ButtonPlay} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonDown} - \opt{HAVEREMOTEKEYMAP}{& } - & Zoom out\\ - \opt{RECORDER_PAD}{\ButtonFThree} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonRight} - \opt{IRIVER_H100_PAD}{\ButtonOn} - \opt{IRIVER_H300_PAD}{\ButtonRec} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonRight} - \opt{IAUDIO_X5_PAD}{\ButtonPlay} - \opt{IRIVER_H10_PAD}{\ButtonFF} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollFwd} - \opt{SANSA_C200_PAD}{\ButtonVolUp} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonRight} - \opt{GIGABEAT_S_PAD}{\ButtonNext} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonRight} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonRight} - \opt{HAVEREMOTEKEYMAP}{& } - & Next png in directory\\ - \opt{RECORDER_PAD}{\ButtonFTwo} - \opt{ONDIO_PAD}{\ButtonMenu+\ButtonLeft} - \opt{IRIVER_H100_PAD,IAUDIO_X5_PAD}{\ButtonRec} - \opt{IRIVER_H300_PAD}{\ButtonOn} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonLeft} - \opt{IRIVER_H10_PAD}{\ButtonRew} - \opt{SANSA_E200_PAD,SANSA_FUZE_PAD}{\ButtonScrollBack} - \opt{SANSA_C200_PAD}{\ButtonVolDown} - \opt{GIGABEAT_PAD}{\ButtonA+\ButtonLeft} - \opt{GIGABEAT_S_PAD}{\ButtonPrev} - \opt{MROBE100_PAD}{\ButtonDisplay+\ButtonLeft} - \opt{PBELL_VIBE500_PAD}{\ButtonRec+\ButtonLeft} - \opt{HAVEREMOTEKEYMAP}{& } - & Previous png in directory\\ - \opt{SANSA_E200_PAD,SANSA_C200_PAD}{%currently only defined for the sansa pads - \ButtonRec - \opt{HAVEREMOTEKEYMAP}{& } - & Toggle slide show mode\\ - } - \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff} - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonMenu} - \opt{IAUDIO_X5_PAD,IRIVER_H10_PAD,SANSA_E200_PAD,SANSA_C200_PAD}{\ButtonPower} - \opt{SANSA_FUZE_PAD}{Long \ButtonHome} - \opt{GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD}{\ButtonMenu} - \opt{HAVEREMOTEKEYMAP}{& - \opt{IRIVER_RC_H100_PAD}{\ButtonRCStop} - } - & Show menu / Abort while decoding \\ - \opt{IPOD_4G_PAD,IPOD_3G_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD% - ,PBELL_VIBE500_PAD}{% - \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonSelect+\ButtonPlay} - \opt{GIGABEAT_PAD,MROBE100_PAD}{\ButtonPower} - \opt{GIGABEAT_S_PAD}{\ButtonBack} - \opt{PBELL_VIBE500_PAD}{\ButtonCancel} - \opt{HAVEREMOTEKEYMAP}{& } - & Quit the viewer \\ - } -\end{btnmap} - -The menu has the following entries. -\begin{description} -\item[Return.] Returns you to the image -\item[Toggle Slideshow Mode.] Enables or disables the slideshow mode. -\item[Change Slideshow Timeout.] You can set the timeout for the slideshow - between 1 second and 20 seconds. -\opt{large_plugin_buffer}{ -\item[Show Playback Menu.] From the playback menu you can control the -playback of the currently loaded playlist and change the volume of your \dap. -} -\item[Quit.] Quits the viewer and returns to the \setting{File Browser}. -\end{description} diff --git a/tools/buildzip.pl b/tools/buildzip.pl index 62413acd30..a3c0172773 100755 --- a/tools/buildzip.pl +++ b/tools/buildzip.pl @@ -470,8 +470,11 @@ STOP find(find_copyfile(qr/\.(rock|ovl|lua)/, abs_path("$temp_dir/rocks/")), 'apps/plugins'); - open VIEWERS, "$ROOT/apps/plugins/viewers.config" or - die "can't open viewers.config"; + # exclude entries for the image file types not supported by the imageviewer for the target. + my $viewers = "$ROOT/apps/plugins/viewers.config"; + my $c="cat $viewers | gcc $cppdef -I. -I$firmdir/export -E -P -include config.h -"; + + open VIEWERS, "$c|" or die "can't open viewers.config"; my @viewers = ; close VIEWERS; -- cgit v1.2.3