From 5621fd393a4445645b07bb2e89d89d23bb9a6473 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 May 2009 01:23:13 +0000 Subject: Build pictureflow using overlay on lowmem targets, support JPEG AA in PF on all targets. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20864 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/SOURCES | 5 +- apps/plugins/SUBDIRS | 3 + apps/plugins/pictureflow.c | 2580 ----------------------------- apps/plugins/pictureflow/SOURCES | 1 + apps/plugins/pictureflow/pictureflow.c | 2575 ++++++++++++++++++++++++++++ apps/plugins/pictureflow/pictureflow.make | 58 + 6 files changed, 2638 insertions(+), 2584 deletions(-) delete mode 100644 apps/plugins/pictureflow.c create mode 100644 apps/plugins/pictureflow/SOURCES create mode 100644 apps/plugins/pictureflow/pictureflow.c create mode 100644 apps/plugins/pictureflow/pictureflow.make (limited to 'apps/plugins') diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index e8d76441b5..a695478e40 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -55,10 +55,6 @@ text_editor.c wavview.c robotfindskitten.c -#if defined(HAVE_TAGCACHE) -pictureflow.c -#endif - #ifdef HAVE_LCD_COLOR ppmviewer.c #endif @@ -109,6 +105,7 @@ zxbox.c /* overlay loader for Goban */ #if (PLUGIN_BUFFER_SIZE < 0x10000) && !defined(SIMULATOR) goban.c +pictureflow.c #endif /* not support recorder models for now */ diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index af5e1d0c0e..597df41b8f 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -16,6 +16,9 @@ rockboy /* For all targets with a bitmap display */ #ifdef HAVE_LCD_BITMAP +#ifdef HAVE_TAGCACHE +pictureflow +#endif chessbox jpeg sudoku diff --git a/apps/plugins/pictureflow.c b/apps/plugins/pictureflow.c deleted file mode 100644 index 7261d7a402..0000000000 --- a/apps/plugins/pictureflow.c +++ /dev/null @@ -1,2580 +0,0 @@ -/*************************************************************************** -* __________ __ ___. -* Open \______ \ ____ ____ | | _\_ |__ _______ ___ -* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / -* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < -* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ -* \/ \/ \/ \/ \/ -* $Id$ -* -* Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st) -* Copyright (C) 2007 Nicolas Pennequin -* Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version) -* -* Original code: http://code.google.com/p/pictureflow/ -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* of the License, or (at your option) any later version. -* -* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -* KIND, either express or implied. -* -****************************************************************************/ - -#include "plugin.h" -#include -#include "lib/read_image.h" -#include "lib/pluginlib_actions.h" -#include "lib/helper.h" -#include "lib/configfile.h" -#include "lib/picture.h" -#include "pluginbitmaps/pictureflow_logo.h" -#include "lib/grey.h" -#include "lib/feature_wrappers.h" -#include "lib/buflib.h" - -PLUGIN_HEADER - -/******************************* Globals ***********************************/ - -#define PF_PREV ACTION_STD_PREV -#define PF_PREV_REPEAT ACTION_STD_PREVREPEAT -#define PF_NEXT ACTION_STD_NEXT -#define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT -#define PF_SELECT ACTION_STD_OK -#define PF_CONTEXT ACTION_STD_CONTEXT -#define PF_BACK ACTION_STD_CANCEL -#define PF_MENU ACTION_STD_MENU -#define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1) - -const struct button_mapping pf_context_album_scroll[] = -{ -#ifdef HAVE_TOUCHSCREEN - {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE}, - {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE}, - {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE}, - {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE}, -#endif -#if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \ - CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \ - CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \ - CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \ - CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \ - CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \ - CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \ - CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \ - CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \ - || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \ - CONFIG_KEYPAD == MEIZU_M6SL_PAD - {PF_PREV, BUTTON_LEFT, BUTTON_NONE}, - {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, - {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE}, - {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, -#elif CONFIG_KEYPAD == ONDIO_PAD - {PF_PREV, BUTTON_LEFT, BUTTON_NONE}, - {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, - {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE}, - {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, - {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP}, - {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP}, - {ACTION_NONE, BUTTON_UP, BUTTON_NONE}, - {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE}, - {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, - {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT}, -#elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD - {PF_PREV, BUTTON_RC_REW, BUTTON_NONE}, - {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE}, - {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE}, - {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE}, -#endif - LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1) -}; - -const struct button_mapping pf_context_buttons[] = -{ -#ifdef HAVE_TOUCHSCREEN - {PF_SELECT, BUTTON_CENTER, BUTTON_NONE}, - {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE}, - {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE}, -#endif -#if CONFIG_KEYPAD == ARCHOS_AV300_PAD - {PF_QUIT, BUTTON_OFF, BUTTON_NONE}, -#elif CONFIG_KEYPAD == SANSA_C100_PAD - {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, -#elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \ - CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \ - CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \ - CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \ - CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \ - CONFIG_KEYPAD == SANSA_FUZE_PAD - {PF_QUIT, BUTTON_POWER, BUTTON_NONE}, -/* These all use short press of BUTTON_POWER for menu, map long POWER to quit -*/ -#elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \ - CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD - {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER}, -#if CONFIG_KEYPAD == COWOND2_PAD - {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER}, - {ACTION_NONE, BUTTON_POWER, BUTTON_NONE}, -#endif -#elif CONFIG_KEYPAD == SANSA_E200_PAD - {PF_QUIT, BUTTON_POWER, BUTTON_NONE}, -#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD - {PF_QUIT, BUTTON_EQ, BUTTON_NONE}, -#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ - || (CONFIG_KEYPAD == IPOD_3G_PAD) \ - || (CONFIG_KEYPAD == IPOD_4G_PAD) - {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, -#elif CONFIG_KEYPAD == LOGIK_DAX_PAD - {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY}, -#elif CONFIG_KEYPAD == IAUDIO_M3_PAD - {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE}, -#elif CONFIG_KEYPAD == MEIZU_M6SL_PAD - {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, -#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \ - CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD - {PF_QUIT, BUTTON_OFF, BUTTON_NONE}, -#endif -#if CONFIG_KEYPAD == IAUDIO_M3_PAD - LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE) -#else - LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) -#endif -}; -const struct button_mapping *pf_contexts[] = -{ - pf_context_album_scroll, - pf_context_buttons -}; - -#if LCD_DEPTH < 8 -#if LCD_DEPTH > 1 -#define N_BRIGHT(y) LCD_BRIGHTNESS(y) -#else /* LCD_DEPTH <= 1 */ -#define N_BRIGHT(y) ((y > 127) ? 0 : 1) -#ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */ -#define PICTUREFLOW_DRMODE DRMODE_SOLID -#else -#define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID) -#endif -#endif /* LCD_DEPTH <= 1 */ -#define USEGSLIB -GREY_INFO_STRUCT -#define LCD_BUF _grey_info.buffer -#define MYLCD(fn) grey_ ## fn -#define G_PIX(r,g,b) \ - (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256 -#define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b)) -#define G_BRIGHT(y) (y) -#define BUFFER_WIDTH _grey_info.width -#define BUFFER_HEIGHT _grey_info.height -typedef unsigned char pix_t; -#else /* LCD_DEPTH >= 8 */ -#define LCD_BUF rb->lcd_framebuffer -#define MYLCD(fn) rb->lcd_ ## fn -#define G_PIX LCD_RGBPACK -#define N_PIX LCD_RGBPACK -#define G_BRIGHT(y) LCD_RGBPACK(y,y,y) -#define N_BRIGHT(y) LCD_RGBPACK(y,y,y) -#define BUFFER_WIDTH LCD_WIDTH -#define BUFFER_HEIGHT LCD_HEIGHT -typedef fb_data pix_t; -#endif /* LCD_DEPTH >= 8 */ - -/* for fixed-point arithmetic, we need minimum 32-bit long - long long (64-bit) might be useful for multiplication and division */ -#define PFreal long -#define PFREAL_SHIFT 10 -#define PFREAL_FACTOR (1 << PFREAL_SHIFT) -#define PFREAL_ONE (1 << PFREAL_SHIFT) -#define PFREAL_HALF (PFREAL_ONE >> 1) - - -#define IANGLE_MAX 1024 -#define IANGLE_MASK 1023 - -#define REFLECT_TOP (LCD_HEIGHT * 2 / 3) -#define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP) -#define DISPLAY_HEIGHT REFLECT_TOP -#define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \ - LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5)) -#define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \ - (REFLECT_HEIGHT * 5)) -#define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT) -#define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120) -#define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT) -#define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF) -#define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF) - -#define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */ - -#define MAX_SLIDES_COUNT 10 - -#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200 -#define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow" - -#define EV_EXIT 9999 -#define EV_WAKEUP 1337 - -/* maximum number of albums */ - -#define MAX_TRACKS 50 -#define AVG_TRACK_NAME_LENGTH 20 - - -#define UNIQBUF_SIZE (64*1024) - -#define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw" -#define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp" - -/* Error return values */ -#define ERROR_NO_ALBUMS -1 -#define ERROR_BUFFER_FULL -2 - -/* current version for cover cache */ -#define CACHE_VERSION 2 -#define CONFIG_VERSION 1 -#define CONFIG_FILE "pictureflow.cfg" - -/** structs we use */ - -struct slide_data { - int slide_index; - int angle; - PFreal cx; - PFreal cy; - PFreal distance; -}; - -struct slide_cache { - int index; /* index of the cached slide */ - int hid; /* handle ID of the cached slide */ - short next; /* "next" slide, with LRU last */ - short prev; /* "previous" slide */ -}; - -struct album_data { - int name_idx; - long seek; -}; - -struct track_data { - int name_idx; - long seek; -}; - -struct rect { - int left; - int right; - int top; - int bottom; -}; - -struct load_slide_event_data { - int slide_index; - int cache_index; -}; - - -struct pfraw_header { - int32_t width; /* bmap width in pixels */ - int32_t height; /* bmap height in pixels */ -}; - -const struct picture logos[]={ - {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo}, -}; - -enum show_album_name_values { album_name_hide = 0, album_name_bottom, - album_name_top }; -static char* show_album_name_conf[] = -{ - "hide", - "bottom", - "top" -}; - -#define MAX_SPACING 40 -#define MAX_MARGIN 80 - -/* config values and their defaults */ -static int slide_spacing = DISPLAY_WIDTH / 4; -static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12; -static int num_slides = 4; -static int zoom = 100; -static bool show_fps = false; -static bool resize = true; -static int cache_version = 0; -static int show_album_name = (LCD_HEIGHT > 100) - ? album_name_top : album_name_bottom; - -static struct configdata config[] = -{ - { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing", - NULL }, - { TYPE_INT, 0, MAX_MARGIN, { .int_p = ¢er_margin }, "center margin", - NULL }, - { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count", - NULL }, - { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL }, - { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL }, - { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL }, - { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL }, - { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name", - show_album_name_conf } -}; - -#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata)) - -/** below we allocate the memory we want to use **/ - -static pix_t *buffer; /* for now it always points to the lcd framebuffer */ -static uint8_t reflect_table[REFLECT_HEIGHT]; -static struct slide_data center_slide; -static struct slide_data left_slides[MAX_SLIDES_COUNT]; -static struct slide_data right_slides[MAX_SLIDES_COUNT]; -static int slide_frame; -static int step; -static int target; -static int fade; -static int center_index = 0; /* index of the slide that is in the center */ -static int itilt; -static PFreal offsetX; -static PFreal offsetY; -static int number_of_slides; - -static struct slide_cache cache[SLIDE_CACHE_SIZE]; -static int cache_free; -static int cache_used = -1; -static int cache_left_index = -1; -static int cache_right_index = -1; -static int cache_center_index = -1; - -/* use long for aligning */ -unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)]; -/* queue (as array) for scheduling load_surface */ - -static int empty_slide_hid; - -unsigned int thread_id; -struct event_queue thread_q; - -static struct tagcache_search tcs; - -static struct buflib_context buf_ctx; - -static struct album_data *album; -static char *album_names; -static int album_count; - -static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH]; -static struct track_data tracks[MAX_TRACKS]; -static int track_count; -static int track_index; -static int selected_track; -static int selected_track_pulse; -void reset_track_list(void); - -void * buf; -size_t buf_size; - -static bool thread_is_running; - -static int cover_animation_keyframe; -static int extra_fade; - -static int albumtxt_x = 0; -static int albumtxt_dir = -1; -static int prev_center_index = -1; - -static int start_index_track_list = 0; -static int track_list_visible_entries = 0; -static int track_list_y; -static int track_list_h; -static int track_scroll_index = 0; -static int track_scroll_dir = 1; - -/* - Proposals for transitions: - - pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed - -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked - - pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released - - pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed - - TODO: - pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed - pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed - - pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED -*/ -enum pf_states { - pf_idle = 0, - pf_scrolling, - pf_cover_in, - pf_show_tracks, - pf_cover_out -}; - -static int pf_state; - -/** code */ -static inline unsigned fade_color(pix_t c, unsigned a); -bool save_pfraw(char* filename, struct bitmap *bm); -bool load_new_slide(void); -int load_surface(int); - -static inline PFreal fmul(PFreal a, PFreal b) -{ - return (a*b) >> PFREAL_SHIFT; -} - -/** - * This version preshifts each operand, which is useful when we know how many - * of the least significant bits will be empty, or are worried about overflow - * in a particular calculation - */ -static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2) -{ - return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2); -} - -/* ARMv5+ has a clz instruction equivalent to our function. - */ -#if (defined(CPU_ARM) && (ARM_ARCH > 4)) -static inline int clz(uint32_t v) -{ - return __builtin_clz(v); -} - -/* Otherwise, use our clz, which can be inlined */ -#elif defined(CPU_COLDFIRE) -/* This clz is based on the log2(n) implementation at - * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog - * A clz benchmark plugin showed this to be about 14% faster on coldfire - * than the LUT-based version. - */ -static inline int clz(uint32_t v) -{ - int r = 32; - if (v >= 0x10000) - { - v >>= 16; - r -= 16; - } - if (v & 0xff00) - { - v >>= 8; - r -= 8; - } - if (v & 0xf0) - { - v >>= 4; - r -= 4; - } - if (v & 0xc) - { - v >>= 2; - r -= 2; - } - if (v & 2) - { - v >>= 1; - r -= 1; - } - r -= v; - return r; -} -#else -static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0 }; -/* This clz is based on the log2(n) implementation at - * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup - * It is not any faster than the one above, but trades 16B in the lookup table - * for a savings of 12B per each inlined call. - */ -static inline int clz(uint32_t v) -{ - int r = 28; - if (v >= 0x10000) - { - v >>= 16; - r -= 16; - } - if (v & 0xff00) - { - v >>= 8; - r -= 8; - } - if (v & 0xf0) - { - v >>= 4; - r -= 4; - } - return r + clz_lut[v]; -} -#endif - -/* Return the maximum possible left shift for a signed int32, without - * overflow - */ -static inline int allowed_shift(int32_t val) -{ - uint32_t uval = val ^ (val >> 31); - return clz(uval) - 1; -} - -/* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting - * num and den before dividing. - */ -static inline PFreal fdiv(PFreal num, PFreal den) -{ - int shift = allowed_shift(num); - shift = MIN(PFREAL_SHIFT, shift); - num <<= shift; - den >>= PFREAL_SHIFT - shift; - return num / den; -} - -#define fmin(a,b) (((a) < (b)) ? (a) : (b)) -#define fmax(a,b) (((a) > (b)) ? (a) : (b)) -#define fabs(a) (a < 0 ? -a : a) -#define fbound(min,val,max) (fmax((min),fmin((max),(val)))) - -#if CONFIG_CPU == SH7034 -/* 16*16->32 bit multiplication is a single instrcution on the SH1 */ -#define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b)))) -#else -#define MULUQ(a, b) ((a) * (b)) -#endif - - -#if 0 -#define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT ) -#define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m ) - -#define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2))) -#define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) ) - -static inline PFreal fmul(PFreal a, PFreal b) -{ - return (a*b) >> PFREAL_SHIFT; -} - -static inline PFreal fdiv(PFreal n, PFreal m) -{ - return (n<<(PFREAL_SHIFT))/m; -} -#endif - -/* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */ -static const short sin_tab[] = { - 0, 100, 200, 297, 392, 483, 569, 650, - 724, 792, 851, 903, 946, 980, 1004, 1019, - 1024, 1019, 1004, 980, 946, 903, 851, 792, - 724, 650, 569, 483, 392, 297, 200, 100, - 0, -100, -200, -297, -392, -483, -569, -650, - -724, -792, -851, -903, -946, -980, -1004, -1019, - -1024, -1019, -1004, -980, -946, -903, -851, -792, - -724, -650, -569, -483, -392, -297, -200, -100, - 0 -}; - -static inline PFreal fsin(int iangle) -{ - iangle &= IANGLE_MASK; - - int i = (iangle >> 4); - PFreal p = sin_tab[i]; - PFreal q = sin_tab[(i+1)]; - PFreal g = (q - p); - return p + g * (iangle-i*16)/16; -} - -static inline PFreal fcos(int iangle) -{ - return fsin(iangle + (IANGLE_MAX >> 2)); -} - -static inline uint32_t div255(uint32_t val) -{ - return ((((val >> 8) + val) >> 8) + val) >> 8; -} - -#define SCALE_VAL(val,out) div255((val) * (out) + 127) - -static void output_row_transposed(uint32_t row, void * row_in, - struct scaler_context *ctx) -{ - pix_t *dest = (pix_t*)ctx->bm->data + row; - pix_t *end = dest + ctx->bm->height * ctx->bm->width; -#ifdef USEGSLIB - uint32_t *qp = (uint32_t*)row_in; - for (; dest < end; dest += ctx->bm->height) - *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor); -#else - struct uint32_rgb *qp = (struct uint32_rgb*)row_in; - uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31), - rb_rnd = SCALE_VAL(ctx->round, 31), - g_mul = SCALE_VAL(ctx->divisor, 63), - g_rnd = SCALE_VAL(ctx->round, 63); - int r, g, b; - for (; dest < end; dest += ctx->bm->height) - { - r = SC_MUL(qp->r + rb_rnd, rb_mul); - g = SC_MUL(qp->g + g_rnd, g_mul); - b = SC_MUL(qp->b + rb_rnd, rb_mul); - qp++; - *dest = LCD_RGBPACK_LCD(r,g,b); - } -#endif -} - -#ifdef HAVE_LCD_COLOR -static void output_row_transposed_fromyuv(uint32_t row, void * row_in, - struct scaler_context *ctx) -{ - pix_t *dest = (pix_t*)ctx->bm->data + row; - pix_t *end = dest + ctx->bm->height * ctx->bm->width; - struct uint32_rgb *qp = (struct uint32_rgb*)row_in; - for (; dest < end; dest += ctx->bm->height) - { - unsigned r, g, b, y, u, v; - y = SC_MUL(qp->b + ctx->round, ctx->divisor); - u = SC_MUL(qp->g + ctx->round, ctx->divisor); - v = SC_MUL(qp->r + ctx->round, ctx->divisor); - qp++; - yuv_to_rgb(y, u, v, &r, &g, &b); - r = (31 * r + (r >> 3) + 127) >> 8; - g = (63 * g + (g >> 2) + 127) >> 8; - b = (31 * b + (b >> 3) + 127) >> 8; - *dest = LCD_RGBPACK_LCD(r, g, b); - } -} -#endif - -static unsigned int get_size(struct bitmap *bm) -{ - return bm->width * bm->height * sizeof(pix_t); -} - -const struct custom_format format_transposed = { -#ifdef HAVE_LCD_COLOR - .output_row = { - output_row_transposed, - output_row_transposed_fromyuv - }, -#else - .output_row = output_row_transposed, -#endif - .get_size = get_size -}; - -static const struct button_mapping* get_context_map(int context) -{ - return pf_contexts[context & ~CONTEXT_CUSTOM]; -} - -/* Create the lookup table with the scaling values for the reflections */ -void init_reflect_table(void) -{ - int i; - for (i = 0; i < REFLECT_HEIGHT; i++) - reflect_table[i] = - (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) / - (5 * REFLECT_HEIGHT); -} - -/** - Create an index of all albums from the database. - Also store the album names so we can access them later. - */ -int create_album_index(void) -{ - buf_size -= UNIQBUF_SIZE * sizeof(long); - long *uniqbuf = (long *)(buf_size + (char *)buf); - album = ((struct album_data *)uniqbuf) - 1; - rb->memset(&tcs, 0, sizeof(struct tagcache_search) ); - album_count = 0; - rb->tagcache_search(&tcs, tag_album); - rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE); - unsigned int l, old_l = 0; - album_names = buf; - album[0].name_idx = 0; - while (rb->tagcache_get_next(&tcs)) - { - buf_size -= sizeof(struct album_data); - l = rb->strlen(tcs.result) + 1; - if ( album_count > 0 ) - album[-album_count].name_idx = album[1-album_count].name_idx + old_l; - - if ( l > buf_size ) - /* not enough memory */ - return ERROR_BUFFER_FULL; - - rb->strcpy(buf, tcs.result); - buf_size -= l; - buf = l + (char *)buf; - album[-album_count].seek = tcs.result_seek; - old_l = l; - album_count++; - } - rb->tagcache_search_finish(&tcs); - ALIGN_BUFFER(buf, buf_size, 4); - int i; - struct album_data* tmp_album = (struct album_data*)buf; - for (i = album_count - 1; i >= 0; i--) - tmp_album[i] = album[-i]; - album = tmp_album; - buf = album + album_count; - buf_size += UNIQBUF_SIZE * sizeof(long); - return (album_count > 0) ? 0 : ERROR_NO_ALBUMS; -} - -/** - Return a pointer to the album name of the given slide_index - */ -char* get_album_name(const int slide_index) -{ - return album_names + album[slide_index].name_idx; -} - -/** - Return a pointer to the track name of the active album - create_track_index has to be called first. - */ -char* get_track_name(const int track_index) -{ - if ( track_index < track_count ) - return track_names + tracks[track_index].name_idx; - return 0; -} - -/** - Create the track index of the given slide_index. - */ -int create_track_index(const int slide_index) -{ - if ( slide_index == track_index ) { - return -1; - } - - if (!rb->tagcache_search(&tcs, tag_title)) - return -1; - - int ret = 0; - char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4]; - int temp_seeks[MAX_TRACKS]; - - rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek); - track_count=0; - int string_index = 0; - int l, track_num, heighest_index = 0; - - for(l=0;ltagcache_get_next(&tcs) && track_count < MAX_TRACKS) - { - track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1; - if (track_num >= 0) - { - rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]), - "%d: %s", track_num+1, tcs.result); - temp_seeks[track_num] = tcs.result_seek; - } - else - { - track_num = 0; - while (temp_titles[track_num][0] != '\0') - track_num++; - rb->strcpy(temp_titles[track_num], tcs.result); - temp_seeks[track_num] = tcs.result_seek; - } - if (track_num > heighest_index) - heighest_index = track_num; - track_count++; - } - - rb->tagcache_search_finish(&tcs); - track_index = slide_index; - - /* now fix the track list order */ - l = 0; - track_count = 0; - while (l <= heighest_index && - string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH) - { - if (temp_titles[l][0] != '\0') - { - rb->strcpy(track_names + string_index, temp_titles[l]); - tracks[track_count].name_idx = string_index; - tracks[track_count].seek = temp_seeks[l]; - string_index += rb->strlen(temp_titles[l]) + 1; - track_count++; - } - l++; - } - if (ret != 0) - return ret; - else - return (track_count > 0) ? 0 : -1; -} - -/** - Determine filename of the album art for the given slide_index and - store the result in buf. - The algorithm looks for the first track of the given album uses - find_albumart to find the filename. - */ -bool get_albumart_for_index_from_db(const int slide_index, char *buf, - int buflen) -{ - if ( slide_index == -1 ) - { - rb->strncpy( buf, EMPTY_SLIDE, buflen ); - } - - if (!rb->tagcache_search(&tcs, tag_filename)) - return false; - - bool result; - /* find the first track of the album */ - rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek); - - if ( rb->tagcache_get_next(&tcs) ) { - struct mp3entry id3; - int fd; - - fd = rb->open(tcs.result, O_RDONLY); - rb->get_metadata(&id3, fd, tcs.result); - rb->close(fd); - if ( search_albumart_files(&id3, "", buf, buflen) ) - result = true; - else - result = false; - } - else { - /* did not find a matching track */ - result = false; - } - rb->tagcache_search_finish(&tcs); - return result; -} - -/** - Draw the PictureFlow logo - */ -void draw_splashscreen(void) -{ - struct screen* display = rb->screens[0]; - const struct picture* logo = &(logos[display->screen_type]); - -#if LCD_DEPTH > 1 - rb->lcd_set_background(N_BRIGHT(0)); - rb->lcd_set_foreground(N_BRIGHT(255)); -#else - rb->lcd_set_drawmode(PICTUREFLOW_DRMODE); -#endif - rb->lcd_clear_display(); - -#if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */ - rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID); - picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10); - rb->lcd_set_drawmode(PICTUREFLOW_DRMODE); -#else - picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10); -#endif - - rb->lcd_update(); -} - - -/** - Draw a simple progress bar - */ -void draw_progressbar(int step) -{ - int txt_w, txt_h; - const int bar_height = 22; - const int w = LCD_WIDTH - 20; - const int x = 10; - - rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h); - - int y = (LCD_HEIGHT - txt_h)/2; - - rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork"); - y += (txt_h + 5); - -#if LCD_DEPTH > 1 - rb->lcd_set_foreground(N_BRIGHT(100)); -#endif - rb->lcd_drawrect(x, y, w+2, bar_height); -#if LCD_DEPTH > 1 - rb->lcd_set_foreground(N_PIX(165, 231, 82)); -#endif - - rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2); -#if LCD_DEPTH > 1 - rb->lcd_set_foreground(N_BRIGHT(255)); -#endif - rb->lcd_update(); - rb->yield(); -} - -/** - Precomupte the album art images and store them in CACHE_PREFIX. - */ -bool create_albumart_cache(void) -{ - int ret; - - int i, slides = 0; - struct bitmap input_bmp; - - char pfraw_file[MAX_PATH]; - char albumart_file[MAX_PATH]; - unsigned int format = FORMAT_NATIVE; - cache_version = 0; - configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); - if (resize) - format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT; - for (i=0; i < album_count; i++) - { - rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw", - i); - /* delete existing cache, so it's a true rebuild */ - if(rb->file_exists(pfraw_file)) - rb->remove(pfraw_file); - draw_progressbar(i); - if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH)) - continue; - - input_bmp.data = buf; - input_bmp.width = DISPLAY_WIDTH; - input_bmp.height = DISPLAY_HEIGHT; -#if PLUGIN_BUFFER_SIZE > 0x10000 - ret = read_image_file(albumart_file, &input_bmp, - buf_size, format, &format_transposed); -#else - ret = scaled_read_bmp_file(albumart_file, &input_bmp, - buf_size, format, &format_transposed); -#endif - if (ret <= 0) { - rb->splash(HZ, "Could not read bmp"); - continue; /* skip missing/broken files */ - } - if (!save_pfraw(pfraw_file, &input_bmp)) - { - rb->splash(HZ, "Could not write bmp"); - } - slides++; - if ( rb->button_get(false) == PF_MENU ) return false; - } - if ( slides == 0 ) { - /* Warn the user that we couldn't find any albumart */ - rb->splash(2*HZ, "No album art found"); - return false; - } - return true; -} - -/** - Thread used for loading and preparing bitmaps in the background - */ -void thread(void) -{ - long sleep_time = 5 * HZ; - struct queue_event ev; - while (1) { - rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time); - switch (ev.id) { - case EV_EXIT: - return; - case EV_WAKEUP: - /* we just woke up */ - break; - } - while ( load_new_slide() ) { - rb->yield(); - switch (ev.id) { - case EV_EXIT: - return; - } - } - } -} - - -/** - End the thread by posting the EV_EXIT event - */ -void end_pf_thread(void) -{ - if ( thread_is_running ) { - rb->queue_post(&thread_q, EV_EXIT, 0); - rb->thread_wait(thread_id); - /* remove the thread's queue from the broadcast list */ - rb->queue_delete(&thread_q); - thread_is_running = false; - } - -} - - -/** - Create the thread an setup the event queue - */ -bool create_pf_thread(void) -{ - /* put the thread's queue in the bcast list */ - rb->queue_init(&thread_q, true); - if ((thread_id = rb->create_thread( - thread, - thread_stack, - sizeof(thread_stack), - 0, - "Picture load thread" - IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2, - PRIORITY_REALTIME + 1)) - IF_COP(, CPU) - ) - ) == 0) { - return false; - } - thread_is_running = true; - rb->queue_post(&thread_q, EV_WAKEUP, 0); - return true; -} - -/** - Safe the given bitmap as filename in the pfraw format - */ -bool save_pfraw(char* filename, struct bitmap *bm) -{ - struct pfraw_header bmph; - bmph.width = bm->width; - bmph.height = bm->height; - int fh = rb->creat( filename ); - if( fh < 0 ) return false; - rb->write( fh, &bmph, sizeof( struct pfraw_header ) ); - int y; - for( y = 0; y < bm->height; y++ ) - { - pix_t *d = (pix_t*)( bm->data ) + (y*bm->width); - rb->write( fh, d, sizeof( pix_t ) * bm->width ); - } - rb->close( fh ); - return true; -} - - -/* - * The following functions implement the linked-list-in-array used to manage - * the LRU cache of slides, and the list of free cache slots. - */ - -#define seek_right_while(start, cond) \ -({ \ - int ind_, next_ = (start); \ - do { \ - ind_ = next_; \ - next_ = cache[ind_].next; \ - } while (next_ != cache_used && (cond)); \ - ind_; \ -}) - -#define seek_left_while(start, cond) \ -({ \ - int ind_, next_ = (start); \ - do { \ - ind_ = next_; \ - next_ = cache[ind_].prev; \ - } while (ind_ != cache_used && (cond)); \ - ind_; \ -}) - -/** - Pop the given item from the linked list starting at *head, returning the next - item, or -1 if the list is now empty. -*/ -static inline int lla_pop_item (int *head, int i) -{ - int prev = cache[i].prev; - int next = cache[i].next; - if (i == next) - { - *head = -1; - return -1; - } - else if (i == *head) - *head = next; - cache[next].prev = prev; - cache[prev].next = next; - return next; -} - - -/** - Pop the head item from the list starting at *head, returning the index of the - item, or -1 if the list is already empty. -*/ -static inline int lla_pop_head (int *head) -{ - int i = *head; - if (i != -1) - lla_pop_item(head, i); - return i; -} - -/** - Insert the item at index i before the one at index p. -*/ -static inline void lla_insert (int i, int p) -{ - int next = p; - int prev = cache[next].prev; - cache[next].prev = i; - cache[prev].next = i; - cache[i].next = next; - cache[i].prev = prev; -} - - -/** - Insert the item at index i at the end of the list starting at *head. -*/ -static inline void lla_insert_tail (int *head, int i) -{ - if (*head == -1) - { - *head = i; - cache[i].next = i; - cache[i].prev = i; - } else - lla_insert(i, *head); -} - -/** - Insert the item at index i before the one at index p. -*/ -static inline void lla_insert_after(int i, int p) -{ - p = cache[p].next; - lla_insert(i, p); -} - - -/** - Insert the item at index i before the one at index p in the list starting at - *head -*/ -static inline void lla_insert_before(int *head, int i, int p) -{ - lla_insert(i, p); - if (*head == p) - *head = i; -} - - -/** - Free the used slide at index i, and its buffer, and move it to the free - slides list. -*/ -static inline void free_slide(int i) -{ - if (cache[i].hid != empty_slide_hid) - buflib_free(&buf_ctx, cache[i].hid); - cache[i].index = -1; - lla_pop_item(&cache_used, i); - lla_insert_tail(&cache_free, i); - if (cache_used == -1) - { - cache_right_index = -1; - cache_left_index = -1; - cache_center_index = -1; - } -} - - -/** - Free one slide ranked above the given priority. If no such slide can be found, - return false. -*/ -static inline int free_slide_prio(int prio) -{ - if (cache_used == -1) - return false; - int i, l = cache_used, r = cache[cache_used].prev, prio_max; - int prio_l = cache[l].index < center_index ? - center_index - cache[l].index : 0; - int prio_r = cache[r].index > center_index ? - cache[r].index - center_index : 0; - if (prio_l > prio_r) - { - i = l; - prio_max = prio_l; - } else { - i = r; - prio_max = prio_r; - } - if (prio_max > prio) - { - if (i == cache_left_index) - cache_left_index = cache[i].next; - if (i == cache_right_index) - cache_right_index = cache[i].prev; - free_slide(i); - return true; - } else - return false; -} - -/** - Read the pfraw image given as filename and return the hid of the buffer - */ -int read_pfraw(char* filename, int prio) -{ - struct pfraw_header bmph; - int fh = rb->open(filename, O_RDONLY); - if( fh < 0 ) - return empty_slide_hid; - else - rb->read(fh, &bmph, sizeof(struct pfraw_header)); - - int size = sizeof(struct bitmap) + sizeof( pix_t ) * - bmph.width * bmph.height; - - int hid; - while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio)); - - if (!hid) { - rb->close( fh ); - return 0; - } - - struct dim *bm = buflib_get_data(&buf_ctx, hid); - - bm->width = bmph.width; - bm->height = bmph.height; - pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm); - - int y; - for( y = 0; y < bm->height; y++ ) - { - rb->read( fh, data , sizeof( pix_t ) * bm->width ); - data += bm->width; - } - rb->close( fh ); - return hid; -} - - -/** - Load the surface for the given slide_index into the cache at cache_index. - */ -static inline bool load_and_prepare_surface(const int slide_index, - const int cache_index, - const int prio) -{ - char tmp_path_name[MAX_PATH+1]; - rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw", - slide_index); - - int hid = read_pfraw(tmp_path_name, prio); - if (!hid) - return false; - - cache[cache_index].hid = hid; - - if ( cache_index < SLIDE_CACHE_SIZE ) { - cache[cache_index].index = slide_index; - } - - return true; -} - - -/** - Load the "next" slide that we can load, freeing old slides if needed, provided - that they are further from center_index than the current slide -*/ -bool load_new_slide(void) -{ - int i = -1; - if (cache_center_index != -1) - { - int next, prev; - if (cache[cache_center_index].index != center_index) - { - if (cache[cache_center_index].index < center_index) - { - cache_center_index = seek_right_while(cache_center_index, - cache[next_].index <= center_index); - prev = cache_center_index; - next = cache[cache_center_index].next; - } - else - { - cache_center_index = seek_left_while(cache_center_index, - cache[next_].index >= center_index); - next = cache_center_index; - prev = cache[cache_center_index].prev; - } - if (cache[cache_center_index].index != center_index) - { - if (cache_free == -1) - free_slide_prio(0); - i = lla_pop_head(&cache_free); - if (!load_and_prepare_surface(center_index, i, 0)) - goto fail_and_refree; - if (cache[next].index == -1) - { - if (cache[prev].index == -1) - goto insert_first_slide; - else - next = cache[prev].next; - } - lla_insert(i, next); - if (cache[i].index < cache[cache_used].index) - cache_used = i; - cache_center_index = i; - cache_left_index = i; - cache_right_index = i; - return true; - } - } - if (cache[cache_left_index].index > - cache[cache_center_index].index) - cache_left_index = cache_center_index; - if (cache[cache_right_index].index < - cache[cache_center_index].index) - cache_right_index = cache_center_index; - cache_left_index = seek_left_while(cache_left_index, - cache[ind_].index - 1 == cache[next_].index); - cache_right_index = seek_right_while(cache_right_index, - cache[ind_].index - 1 == cache[next_].index); - int prio_l = cache[cache_center_index].index - - cache[cache_left_index].index + 1; - int prio_r = cache[cache_right_index].index - - cache[cache_center_index].index + 1; - if ((prio_l < prio_r || - cache[cache_right_index].index >= number_of_slides) && - cache[cache_left_index].index > 0) - { - if (cache_free == -1 && !free_slide_prio(prio_l)) - return false; - i = lla_pop_head(&cache_free); - if (load_and_prepare_surface(cache[cache_left_index].index - - 1, i, prio_l)) - { - lla_insert_before(&cache_used, i, cache_left_index); - cache_left_index = i; - return true; - } - } else if(cache[cache_right_index].index < number_of_slides - 1) - { - if (cache_free == -1 && !free_slide_prio(prio_r)) - return false; - i = lla_pop_head(&cache_free); - if (load_and_prepare_surface(cache[cache_right_index].index - + 1, i, prio_r)) - { - lla_insert_after(i, cache_right_index); - cache_right_index = i; - return true; - } - } - } else { - i = lla_pop_head(&cache_free); - if (load_and_prepare_surface(center_index, i, 0)) - { -insert_first_slide: - cache[i].next = i; - cache[i].prev = i; - cache_center_index = i; - cache_left_index = i; - cache_right_index = i; - cache_used = i; - return true; - } - } -fail_and_refree: - if (i != -1) - { - lla_insert_tail(&cache_free, i); - } - return false; -} - - -/** - Get a slide from the buffer - */ -static inline struct dim *get_slide(const int hid) -{ - if (!hid) - return NULL; - - struct dim *bmp; - - bmp = buflib_get_data(&buf_ctx, hid); - - return bmp; -} - - -/** - Return the requested surface -*/ -static inline struct dim *surface(const int slide_index) -{ - if (slide_index < 0) - return 0; - if (slide_index >= number_of_slides) - return 0; - int i; - if ((i = cache_used ) != -1) - { - do { - if (cache[i].index == slide_index) - return get_slide(cache[i].hid); - i = cache[i].next; - } while (i != cache_used); - } - return get_slide(empty_slide_hid); -} - -/** - adjust slides so that they are in "steady state" position - */ -void reset_slides(void) -{ - center_slide.angle = 0; - center_slide.cx = 0; - center_slide.cy = 0; - center_slide.distance = 0; - center_slide.slide_index = center_index; - - int i; - for (i = 0; i < num_slides; i++) { - struct slide_data *si = &left_slides[i]; - si->angle = itilt; - si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE); - si->cy = offsetY; - si->slide_index = center_index - 1 - i; - si->distance = 0; - } - - for (i = 0; i < num_slides; i++) { - struct slide_data *si = &right_slides[i]; - si->angle = -itilt; - si->cx = offsetX + slide_spacing * i * PFREAL_ONE; - si->cy = offsetY; - si->slide_index = center_index + 1 + i; - si->distance = 0; - } -} - - -/** - Updates look-up table and other stuff necessary for the rendering. - Call this when the viewport size or slide dimension is changed. - * - * To calculate the offset that will provide the proper margin, we use the same - * projection used to render the slides. The solution for xc, the slide center, - * is: - * xp * (zo + xs * sin(r)) - * xc = xp - xs * cos(r) + ─────────────────────── - * z - * TODO: support moving the side slides toward or away from the camera - */ -void recalc_offsets(void) -{ - PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF; - PFreal zo; - PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin * - PFREAL_ONE) * zoom / 100; - PFreal cosr, sinr; - - itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */ - cosr = fcos(-itilt); - sinr = fsin(-itilt); - zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R + - fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0); - offsetX = xp - fmul(xs, cosr) + fmuln(xp, - zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0) - / CAM_DIST; - offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2); -} - - -/** - Fade the given color by spreading the fb_data (ushort) - to an uint, multiply and compress the result back to a ushort. - */ -#if (LCD_PIXELFORMAT == RGB565SWAPPED) -static inline unsigned fade_color(pix_t c, unsigned a) -{ - unsigned int result; - c = swap16(c); - a = (a + 2) & 0x1fc; - result = ((c & 0xf81f) * a) & 0xf81f00; - result |= ((c & 0x7e0) * a) & 0x7e000; - result >>= 8; - return swap16(result); -} -#elif LCD_PIXELFORMAT == RGB565 -static inline unsigned fade_color(pix_t c, unsigned a) -{ - unsigned int result; - a = (a + 2) & 0x1fc; - result = ((c & 0xf81f) * a) & 0xf81f00; - result |= ((c & 0x7e0) * a) & 0x7e000; - result >>= 8; - return result; -} -#else -static inline unsigned fade_color(pix_t c, unsigned a) -{ - unsigned val = c; - return MULUQ(val, a) >> 8; -} -#endif - -/** - * Render a single slide - * Where xc is the slide's horizontal offset from center, xs is the horizontal - * on the slide from its center, zo is the slide's depth offset from the plane - * of the display, r is the angle at which the slide is tilted, and xp is the - * point on the display corresponding to xs on the slide, the projection - * formulas are: - * - * z * (xc + xs * cos(r)) - * xp = ────────────────────── - * z + zo + xs * sin(r) - * - * z * (xc - xp) - xp * zo - * xs = ──────────────────────── - * xp * sin(r) - z * cos(r) - * - * We use the xp projection once, to find the left edge of the slide on the - * display. From there, we use the xs reverse projection to find the horizontal - * offset from the slide center of each column on the screen, until we reach - * the right edge of the slide, or the screen. The reverse projection can be - * optimized by saving the numerator and denominator of the fraction, which can - * then be incremented by (z + zo) and sin(r) respectively. - */ -void render_slide(struct slide_data *slide, const int alpha) -{ - struct dim *bmp = surface(slide->slide_index); - if (!bmp) { - return; - } - if (slide->angle > 255 || slide->angle < -255) - return; - pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp); - - const int sw = bmp->width; - const int sh = bmp->height; - const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF; - const int w = LCD_WIDTH; - - uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */ - - if (alpha == 256) { /* opaque -> copy table */ - rb->memcpy(reftab, reflect_table, sizeof(reftab)); - } else { /* precalculate faded table */ - int i, lalpha; - for (i = 0; i < REFLECT_HEIGHT; i++) { - lalpha = reflect_table[i]; - reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8; - } - } - - PFreal cosr = fcos(slide->angle); - PFreal sinr = fsin(slide->angle); - PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom - - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0); - PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni; - PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)), - (CAM_DIST_R + zo + fmul(xs,sinr))); - - /* Since we're finding the screen position of the left edge of the slide, - * we round up. - */ - int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1) - >> PFREAL_SHIFT; - xp = DISPLAY_LEFT_R + xi * PFREAL_ONE; - if (xi >= w) { - return; - } - xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0); - xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr; - xs = fdiv(xsnum, xsden); - - xsnumi = -CAM_DIST_R - zo; - xsdeni = sinr; - int x; - int dy = PFREAL_ONE; - for (x = xi; x < w; x++) { - int column = (xs - slide_left) / PFREAL_ONE; - if (column >= sw) - break; - if (zo || slide->angle) - dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST; - - const pix_t *ptr = &src[column * bmp->height]; - const int pixelstep = BUFFER_WIDTH; - - int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE; - int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy); - pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x]; - - if (alpha == 256) { - while (p >= plim) { - *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT]; - p -= dy; - pixel -= pixelstep; - } - } else { - while (p >= plim) { - *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha); - p -= dy; - pixel -= pixelstep; - } - } - p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE; - plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy); - int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE, - p + (LCD_HEIGHT/2) * dy); - pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x]; - - if (alpha == 256) { - while (p < plim) { - *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT]; - p += dy; - pixel += pixelstep; - } - } else { - while (p < plim) { - *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha); - p += dy; - pixel += pixelstep; - } - } - while (p < plim2) { - int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh; - int lalpha = reftab[ty]; - *pixel = fade_color(ptr[sh - 1 - ty], lalpha); - p += dy; - pixel += pixelstep; - } - - if (zo || slide->angle) - { - xsnum += xsnumi; - xsden += xsdeni; - xs = fdiv(xsnum, xsden); - } else - xs += PFREAL_ONE; - - } - /* let the music play... */ - rb->yield(); - return; -} - - -/** - Jump the the given slide_index - */ -static inline void set_current_slide(const int slide_index) -{ - int old_center_index = center_index; - step = 0; - center_index = fbound(slide_index, 0, number_of_slides - 1); - if (old_center_index != center_index) - rb->queue_post(&thread_q, EV_WAKEUP, 0); - target = center_index; - slide_frame = slide_index << 16; - reset_slides(); -} - -/** - Start the animation for changing slides - */ -void start_animation(void) -{ - step = (target < center_slide.slide_index) ? -1 : 1; - pf_state = pf_scrolling; -} - -/** - Go to the previous slide - */ -void show_previous_slide(void) -{ - if (step == 0) { - if (center_index > 0) { - target = center_index - 1; - start_animation(); - } - } else if ( step > 0 ) { - target = center_index; - start_animation(); - } else { - target = fmax(0, center_index - 2); - } -} - - -/** - Go to the next slide - */ -void show_next_slide(void) -{ - if (step == 0) { - if (center_index < number_of_slides - 1) { - target = center_index + 1; - start_animation(); - } - } else if ( step < 0 ) { - target = center_index; - start_animation(); - } else { - target = fmin(center_index + 2, number_of_slides - 1); - } -} - - -/** - Render the slides. Updates only the offscreen buffer. -*/ -void render_all_slides(void) -{ - MYLCD(set_background)(G_BRIGHT(0)); - /* TODO: Optimizes this by e.g. invalidating rects */ - MYLCD(clear_display)(); - - int nleft = num_slides; - int nright = num_slides; - - int index; - if (step == 0) { - /* no animation, boring plain rendering */ - for (index = nleft - 2; index >= 0; index--) { - int alpha = (index < nleft - 2) ? 256 : 128; - alpha -= extra_fade; - if (alpha > 0 ) - render_slide(&left_slides[index], alpha); - } - for (index = nright - 2; index >= 0; index--) { - int alpha = (index < nright - 2) ? 256 : 128; - alpha -= extra_fade; - if (alpha > 0 ) - render_slide(&right_slides[index], alpha); - } - } else { - /* the first and last slide must fade in/fade out */ - for (index = nleft - 1; index >= 0; index--) { - int alpha = 256; - if (index == nleft - 1) - alpha = (step > 0) ? 0 : 128 - fade / 2; - if (index == nleft - 2) - alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2; - if (index == nleft - 3) - alpha = (step > 0) ? 256 - fade / 2 : 256; - render_slide(&left_slides[index], alpha); - } - for (index = nright - 1; index >= 0; index--) { - int alpha = (index < nright - 2) ? 256 : 128; - if (index == nright - 1) - alpha = (step > 0) ? fade / 2 : 0; - if (index == nright - 2) - alpha = (step > 0) ? 128 + fade / 2 : fade / 2; - if (index == nright - 3) - alpha = (step > 0) ? 256 : 128 + fade / 2; - render_slide(&right_slides[index], alpha); - } - } - render_slide(¢er_slide, 256); -} - - -/** - Updates the animation effect. Call this periodically from a timer. -*/ -void update_scroll_animation(void) -{ - if (step == 0) - return; - - int speed = 16384; - int i; - - /* deaccelerate when approaching the target */ - if (true) { - const int max = 2 * 65536; - - int fi = slide_frame; - fi -= (target << 16); - if (fi < 0) - fi = -fi; - fi = fmin(fi, max); - - int ia = IANGLE_MAX * (fi - max / 2) / (max * 2); - speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE; - } - - slide_frame += speed * step; - - int index = slide_frame >> 16; - int pos = slide_frame & 0xffff; - int neg = 65536 - pos; - int tick = (step < 0) ? neg : pos; - PFreal ftick = (tick * PFREAL_ONE) >> 16; - - /* the leftmost and rightmost slide must fade away */ - fade = pos / 256; - - if (step < 0) - index++; - if (center_index != index) { - center_index = index; - rb->queue_post(&thread_q, EV_WAKEUP, 0); - slide_frame = index << 16; - center_slide.slide_index = center_index; - for (i = 0; i < num_slides; i++) - left_slides[i].slide_index = center_index - 1 - i; - for (i = 0; i < num_slides; i++) - right_slides[i].slide_index = center_index + 1 + i; - } - - center_slide.angle = (step * tick * itilt) >> 16; - center_slide.cx = -step * fmul(offsetX, ftick); - center_slide.cy = fmul(offsetY, ftick); - - if (center_index == target) { - reset_slides(); - pf_state = pf_idle; - step = 0; - fade = 256; - return; - } - - for (i = 0; i < num_slides; i++) { - struct slide_data *si = &left_slides[i]; - si->angle = itilt; - si->cx = - -(offsetX + slide_spacing * i * PFREAL_ONE + step - * slide_spacing * ftick); - si->cy = offsetY; - } - - for (i = 0; i < num_slides; i++) { - struct slide_data *si = &right_slides[i]; - si->angle = -itilt; - si->cx = - offsetX + slide_spacing * i * PFREAL_ONE - step - * slide_spacing * ftick; - si->cy = offsetY; - } - - if (step > 0) { - PFreal ftick = (neg * PFREAL_ONE) >> 16; - right_slides[0].angle = -(neg * itilt) >> 16; - right_slides[0].cx = fmul(offsetX, ftick); - right_slides[0].cy = fmul(offsetY, ftick); - } else { - PFreal ftick = (pos * PFREAL_ONE) >> 16; - left_slides[0].angle = (pos * itilt) >> 16; - left_slides[0].cx = -fmul(offsetX, ftick); - left_slides[0].cy = fmul(offsetY, ftick); - } - - /* must change direction ? */ - if (target < index) - if (step > 0) - step = -1; - if (target > index) - if (step < 0) - step = 1; -} - - -/** - Cleanup the plugin -*/ -void cleanup(void *parameter) -{ - (void) parameter; - /* Turn on backlight timeout (revert to settings) */ - backlight_use_settings(); /* backlight control in lib/helper.c */ - -#ifdef USEGSLIB - grey_release(); -#endif -} - -/** - Create the "?" slide, that is shown while loading - or when no cover was found. - */ -int create_empty_slide(bool force) -{ - if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) { - struct bitmap input_bmp; - int ret; - input_bmp.width = DISPLAY_WIDTH; - input_bmp.height = DISPLAY_HEIGHT; -#if LCD_DEPTH > 1 - input_bmp.format = FORMAT_NATIVE; -#endif - input_bmp.data = (char*)buf; - ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp, - buf_size, - FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT, - &format_transposed); - if (!save_pfraw(EMPTY_SLIDE, &input_bmp)) - return false; - } - - return true; -} - -/** - Shows the album name setting menu -*/ -int album_name_menu(void) -{ - int selection = show_album_name; - - MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL, - "Hide album title", "Show at the bottom", "Show at the top"); - rb->do_menu(&album_name_menu, &selection, NULL, false); - - show_album_name = selection; - return GO_TO_PREVIOUS; -} - -/** - Shows the settings menu - */ -int settings_menu(void) -{ - int selection = 0; - bool old_val; - - MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS", - "Spacing", "Centre margin", "Number of slides", "Zoom", - "Show album title", "Resize Covers", "Rebuild cache"); - - do { - selection=rb->do_menu(&settings_menu,&selection, NULL, false); - switch(selection) { - case 0: - rb->set_bool("Show FPS", &show_fps); - reset_track_list(); - break; - - case 1: - rb->set_int("Spacing between slides", "", 1, - &slide_spacing, - NULL, 1, 0, 100, NULL ); - recalc_offsets(); - reset_slides(); - break; - - case 2: - rb->set_int("Centre margin", "", 1, - ¢er_margin, - NULL, 1, 0, 80, NULL ); - recalc_offsets(); - reset_slides(); - break; - - case 3: - rb->set_int("Number of slides", "", 1, &num_slides, - NULL, 1, 1, MAX_SLIDES_COUNT, NULL ); - recalc_offsets(); - reset_slides(); - break; - - case 4: - rb->set_int("Zoom", "", 1, &zoom, - NULL, 1, 10, 300, NULL ); - recalc_offsets(); - reset_slides(); - break; - case 5: - album_name_menu(); - reset_track_list(); - recalc_offsets(); - reset_slides(); - break; - case 6: - old_val = resize; - rb->set_bool("Resize Covers", &resize); - if (old_val == resize) /* changed? */ - break; - /* fallthrough if changed, since cache needs to be rebuilt */ - case 7: - cache_version = 0; - rb->remove(EMPTY_SLIDE); - rb->splash(HZ, "Cache will be rebuilt on next restart"); - break; - - case MENU_ATTACHED_USB: - return PLUGIN_USB_CONNECTED; - } - } while ( selection >= 0 ); - configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); - return 0; -} - -/** - Show the main menu - */ -int main_menu(void) -{ - int selection = 0; - int result; - -#if LCD_DEPTH > 1 - rb->lcd_set_foreground(N_BRIGHT(255)); -#endif - - MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL, - "Settings", "Return", "Quit"); - while (1) { - switch (rb->do_menu(&main_menu,&selection, NULL, false)) { - case 0: - result = settings_menu(); - if ( result != 0 ) return result; - break; - - case 1: - return 0; - - case 2: - return -1; - - case MENU_ATTACHED_USB: - return PLUGIN_USB_CONNECTED; - - default: - return 0; - } - } -} - -/** - Animation step for zooming into the current cover - */ -void update_cover_in_animation(void) -{ - cover_animation_keyframe++; - if( cover_animation_keyframe < 20 ) { - center_slide.distance-=5; - center_slide.angle+=1; - extra_fade += 13; - } - else if( cover_animation_keyframe < 35 ) { - center_slide.angle+=16; - } - else { - cover_animation_keyframe = 0; - pf_state = pf_show_tracks; - } -} - -/** - Animation step for zooming out the current cover - */ -void update_cover_out_animation(void) -{ - cover_animation_keyframe++; - if( cover_animation_keyframe <= 15 ) { - center_slide.angle-=16; - } - else if( cover_animation_keyframe < 35 ) { - center_slide.distance+=5; - center_slide.angle-=1; - extra_fade -= 13; - } - else { - cover_animation_keyframe = 0; - pf_state = pf_idle; - } -} - -/** - Draw a blue gradient at y with height h - */ -static inline void draw_gradient(int y, int h) -{ - static int r, inc, c; - inc = (100 << 8) / h; - c = 0; - selected_track_pulse = (selected_track_pulse+1) % 10; - int c2 = selected_track_pulse - 5; - for (r=0; r> 9), c2+100-(c >> 9), - c2+250-(c >> 8))); -#else - MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8))); -#endif - MYLCD(hline)(0, LCD_WIDTH, r+y); - if ( r > h/2 ) - c-=inc; - else - c+=inc; - } -} - - -static void track_list_yh(int char_height) -{ - switch (show_album_name) - { - case album_name_hide: - track_list_y = (show_fps ? char_height : 0); - track_list_h = LCD_HEIGHT - track_list_y; - break; - case album_name_bottom: - track_list_y = (show_fps ? char_height : 0); - track_list_h = LCD_HEIGHT - track_list_y - char_height * 2; - break; - default: /* case album_name_top */ - track_list_y = char_height * 2; - track_list_h = LCD_HEIGHT - track_list_y - - (show_fps ? char_height : 0); - break; - } -} - -/** - Reset the track list after a album change - */ -void reset_track_list(void) -{ - int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight(); - track_list_yh(albumtxt_h); - track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count ); - start_index_track_list = 0; - track_scroll_index = 0; - track_scroll_dir = 1; - selected_track = 0; - - /* let the tracklist start more centered - * if the screen isn't filled with tracks */ - if (track_count*albumtxt_h < track_list_h) - { - track_list_h = track_count * albumtxt_h; - track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2); - } -} - -/** - Display the list of tracks - */ -void show_track_list(void) -{ - MYLCD(clear_display)(); - if ( center_slide.slide_index != track_index ) { - create_track_index(center_slide.slide_index); - reset_track_list(); - } - static int titletxt_w, titletxt_x, color, titletxt_h; - titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight(); - - int titletxt_y = track_list_y; - int track_i; - track_i = start_index_track_list; - for (;track_i < track_list_visible_entries+start_index_track_list; - track_i++) - { - MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL); - titletxt_x = (LCD_WIDTH-titletxt_w)/2; - if ( track_i == selected_track ) { - draw_gradient(titletxt_y, titletxt_h); - MYLCD(set_foreground)(G_BRIGHT(255)); - if (titletxt_w > LCD_WIDTH ) { - if ( titletxt_w + track_scroll_index <= LCD_WIDTH ) - track_scroll_dir = 1; - else if ( track_scroll_index >= 0 ) track_scroll_dir = -1; - track_scroll_index += track_scroll_dir*2; - titletxt_x = track_scroll_index; - } - MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i)); - } - else { - color = 250 - (abs(selected_track - track_i) * 200 / track_count); - MYLCD(set_foreground)(G_BRIGHT(color)); - MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i)); - } - titletxt_y += titletxt_h; - } -} - -void select_next_track(void) -{ - if ( selected_track < track_count - 1 ) { - selected_track++; - track_scroll_index = 0; - track_scroll_dir = 1; - if (selected_track==(track_list_visible_entries+start_index_track_list)) - start_index_track_list++; - } -} - -void select_prev_track(void) -{ - if (selected_track > 0 ) { - if (selected_track==start_index_track_list) start_index_track_list--; - track_scroll_index = 0; - track_scroll_dir = 1; - selected_track--; - } -} - -/** - Draw the current album name - */ -void draw_album_text(void) -{ - if (0 == show_album_name) - return; - - int albumtxt_w, albumtxt_h; - int albumtxt_y = 0; - - char *albumtxt; - int c; - /* Draw album text */ - if ( pf_state == pf_scrolling ) { - c = ((slide_frame & 0xffff )/ 255); - if (step < 0) c = 255-c; - if (c > 128 ) { /* half way to next slide .. still not perfect! */ - albumtxt = get_album_name(center_index+step); - c = (c-128)*2; - } - else { - albumtxt = get_album_name(center_index); - c = (128-c)*2; - } - } - else { - c= 255; - albumtxt = get_album_name(center_index); - } - - MYLCD(set_foreground)(G_BRIGHT(c)); - MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h); - if (center_index != prev_center_index) { - albumtxt_x = 0; - albumtxt_dir = -1; - prev_center_index = center_index; - } - - if (show_album_name == album_name_top) - albumtxt_y = albumtxt_h / 2; - else - albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2; - - if (albumtxt_w > LCD_WIDTH ) { - MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt); - if ( pf_state == pf_idle || pf_state == pf_show_tracks ) { - if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1; - else if ( albumtxt_x >= 0 ) albumtxt_dir = -1; - albumtxt_x += albumtxt_dir; - } - } - else { - MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt); - } - - -} - - -/** - Main function that also contain the main plasma - algorithm. - */ -int main(void) -{ - int ret; - - rb->lcd_setfont(FONT_UI); - draw_splashscreen(); - - if ( ! rb->dir_exists( CACHE_PREFIX ) ) { - if ( rb->mkdir( CACHE_PREFIX ) < 0 ) { - rb->splash(HZ, "Could not create directory " CACHE_PREFIX ); - return PLUGIN_ERROR; - } - } - - configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); - - init_reflect_table(); - - ALIGN_BUFFER(buf, buf_size, 4); - ret = create_album_index(); - if (ret == ERROR_BUFFER_FULL) { - rb->splash(HZ, "Not enough memory for album names"); - return PLUGIN_ERROR; - } else if (ret == ERROR_NO_ALBUMS) { - rb->splash(HZ, "No albums found. Please enable database"); - return PLUGIN_ERROR; - } - - ALIGN_BUFFER(buf, buf_size, 4); - number_of_slides = album_count; - if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) { - rb->splash(HZ, "Could not create album art cache"); - return PLUGIN_ERROR; - } - - if (!create_empty_slide(cache_version != CACHE_VERSION)) { - rb->splash(HZ, "Could not load the empty slide"); - return PLUGIN_ERROR; - } - cache_version = CACHE_VERSION; - configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); - - -#ifdef USEGSLIB - long grey_buf_used; - if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP, - LCD_WIDTH, LCD_HEIGHT, &grey_buf_used)) - { - rb->splash(HZ, "Greylib init failed!"); - return PLUGIN_ERROR; - } - grey_setfont(FONT_UI); - buf_size -= grey_buf_used; - buf = (void*)(grey_buf_used + (char*)buf); -#endif - buflib_init(&buf_ctx, (void *)buf, buf_size); - - if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0))) - { - rb->splash(HZ, "Unable to load empty slide image"); - return PLUGIN_ERROR; - } - - if (!create_pf_thread()) { - rb->splash(HZ, "Cannot create thread!"); - return PLUGIN_ERROR; - } - - int i; - - /* initialize */ - for (i = 0; i < SLIDE_CACHE_SIZE; i++) { - cache[i].hid = 0; - cache[i].index = 0; - cache[i].next = i + 1; - cache[i].prev = i - 1; - } - cache[0].prev = i - 1; - cache[i - 1].next = 0; - cache_free = 0; - buffer = LCD_BUF; - - pf_state = pf_idle; - - track_index = -1; - extra_fade = 0; - slide_frame = 0; - step = 0; - target = 0; - fade = 256; - - recalc_offsets(); - reset_slides(); - - char fpstxt[10]; - int button; - - int frames = 0; - long last_update = *rb->current_tick; - long current_update; - long update_interval = 100; - int fps = 0; - int fpstxt_y; - - bool instant_update; -#ifdef USEGSLIB - grey_show(true); - grey_set_drawmode(DRMODE_FG); -#endif - rb->lcd_set_drawmode(DRMODE_FG); - while (true) { - current_update = *rb->current_tick; - frames++; - - /* Initial rendering */ - instant_update = false; - - /* Handle states */ - switch ( pf_state ) { - case pf_scrolling: - update_scroll_animation(); - render_all_slides(); - instant_update = true; - break; - case pf_cover_in: - update_cover_in_animation(); - render_all_slides(); - instant_update = true; - break; - case pf_cover_out: - update_cover_out_animation(); - render_all_slides(); - instant_update = true; - break; - case pf_show_tracks: - show_track_list(); - break; - case pf_idle: - render_all_slides(); - break; - } - - /* Calculate FPS */ - if (current_update - last_update > update_interval) { - fps = frames * HZ / (current_update - last_update); - last_update = current_update; - frames = 0; - } - /* Draw FPS */ - if (show_fps) - { -#ifdef USEGSLIB - MYLCD(set_foreground)(G_BRIGHT(255)); -#else - MYLCD(set_foreground)(G_PIX(255,0,0)); -#endif - rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps); - if (show_album_name == album_name_top) - fpstxt_y = LCD_HEIGHT - - rb->screens[SCREEN_MAIN]->getcharheight(); - else - fpstxt_y = 0; - MYLCD(putsxy)(0, fpstxt_y, fpstxt); - } - draw_album_text(); - - - /* Copy offscreen buffer to LCD and give time to other threads */ - MYLCD(update)(); - rb->yield(); - - /*/ Handle buttons */ - button = rb->get_custom_action(CONTEXT_CUSTOM| - (pf_state == pf_show_tracks ? 1 : 0), - instant_update ? 0 : HZ/16, - get_context_map); - - switch (button) { - case PF_QUIT: - return PLUGIN_OK; - - case PF_BACK: - if ( pf_state == pf_show_tracks ) - pf_state = pf_cover_out; - if (pf_state == pf_idle || pf_state == pf_scrolling) - return PLUGIN_OK; - break; - - case PF_MENU: -#ifdef USEGSLIB - grey_show(false); -#endif - ret = main_menu(); - if ( ret == -1 ) return PLUGIN_OK; - if ( ret != 0 ) return i; -#ifdef USEGSLIB - grey_show(true); -#endif - MYLCD(set_drawmode)(DRMODE_FG); - break; - - case PF_NEXT: - case PF_NEXT_REPEAT: - if ( pf_state == pf_show_tracks ) - select_next_track(); - if ( pf_state == pf_idle || pf_state == pf_scrolling ) - show_next_slide(); - break; - - case PF_PREV: - case PF_PREV_REPEAT: - if ( pf_state == pf_show_tracks ) - select_prev_track(); - if ( pf_state == pf_idle || pf_state == pf_scrolling ) - show_previous_slide(); - break; - - case PF_SELECT: - if ( pf_state == pf_idle ) { - pf_state = pf_cover_in; - } - break; - - default: - if (rb->default_event_handler_ex(button, cleanup, NULL) - == SYS_USB_CONNECTED) - return PLUGIN_USB_CONNECTED; - break; - } - } - - -} - -/*************************** Plugin entry point ****************************/ - -enum plugin_status plugin_start(const void *parameter) -{ - int ret; - (void) parameter; -#if LCD_DEPTH > 1 - rb->lcd_set_backdrop(NULL); -#endif - /* Turn off backlight timeout */ - backlight_force_on(); /* backlight control in lib/helper.c */ -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(true); -#endif -#if PLUGIN_BUFFER_SIZE > 0x10000 - buf = rb->plugin_get_buffer(&buf_size); -#else - buf = rb->plugin_get_audio_buffer(&buf_size); -#endif - ret = main(); -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(false); -#endif - if ( ret == PLUGIN_OK ) { - if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, - CONFIG_VERSION)) - { - rb->splash(HZ, "Error writing config."); - ret = PLUGIN_ERROR; - } - } - - end_pf_thread(); - cleanup(NULL); - return ret; -} diff --git a/apps/plugins/pictureflow/SOURCES b/apps/plugins/pictureflow/SOURCES new file mode 100644 index 0000000000..7d21cec526 --- /dev/null +++ b/apps/plugins/pictureflow/SOURCES @@ -0,0 +1 @@ +pictureflow.c diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c new file mode 100644 index 0000000000..82dc9748ec --- /dev/null +++ b/apps/plugins/pictureflow/pictureflow.c @@ -0,0 +1,2575 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id$ +* +* Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st) +* Copyright (C) 2007 Nicolas Pennequin +* Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version) +* +* Original code: http://code.google.com/p/pictureflow/ +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +****************************************************************************/ + +#include "plugin.h" +#include +#include "lib/read_image.h" +#include "lib/pluginlib_actions.h" +#include "lib/helper.h" +#include "lib/configfile.h" +#include "lib/picture.h" +#include "pluginbitmaps/pictureflow_logo.h" +#include "lib/grey.h" +#include "lib/feature_wrappers.h" +#include "lib/buflib.h" + +PLUGIN_HEADER + +/******************************* Globals ***********************************/ + +#define PF_PREV ACTION_STD_PREV +#define PF_PREV_REPEAT ACTION_STD_PREVREPEAT +#define PF_NEXT ACTION_STD_NEXT +#define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT +#define PF_SELECT ACTION_STD_OK +#define PF_CONTEXT ACTION_STD_CONTEXT +#define PF_BACK ACTION_STD_CANCEL +#define PF_MENU ACTION_STD_MENU +#define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1) + +const struct button_mapping pf_context_album_scroll[] = +{ +#ifdef HAVE_TOUCHSCREEN + {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE}, + {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE}, + {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE}, + {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE}, +#endif +#if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \ + CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \ + CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \ + CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \ + CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \ + CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \ + CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \ + CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \ + CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \ + || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \ + CONFIG_KEYPAD == MEIZU_M6SL_PAD + {PF_PREV, BUTTON_LEFT, BUTTON_NONE}, + {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE}, + {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, +#elif CONFIG_KEYPAD == ONDIO_PAD + {PF_PREV, BUTTON_LEFT, BUTTON_NONE}, + {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE}, + {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP}, + {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP}, + {ACTION_NONE, BUTTON_UP, BUTTON_NONE}, + {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT}, +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD + {PF_PREV, BUTTON_RC_REW, BUTTON_NONE}, + {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE}, + {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE}, + {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE}, +#endif + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1) +}; + +const struct button_mapping pf_context_buttons[] = +{ +#ifdef HAVE_TOUCHSCREEN + {PF_SELECT, BUTTON_CENTER, BUTTON_NONE}, + {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE}, + {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE}, +#endif +#if CONFIG_KEYPAD == ARCHOS_AV300_PAD + {PF_QUIT, BUTTON_OFF, BUTTON_NONE}, +#elif CONFIG_KEYPAD == SANSA_C100_PAD + {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, +#elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \ + CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \ + CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \ + CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \ + CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \ + CONFIG_KEYPAD == SANSA_FUZE_PAD + {PF_QUIT, BUTTON_POWER, BUTTON_NONE}, +/* These all use short press of BUTTON_POWER for menu, map long POWER to quit +*/ +#elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \ + CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD + {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER}, +#if CONFIG_KEYPAD == COWOND2_PAD + {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER}, + {ACTION_NONE, BUTTON_POWER, BUTTON_NONE}, +#endif +#elif CONFIG_KEYPAD == SANSA_E200_PAD + {PF_QUIT, BUTTON_POWER, BUTTON_NONE}, +#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD + {PF_QUIT, BUTTON_EQ, BUTTON_NONE}, +#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ + || (CONFIG_KEYPAD == IPOD_3G_PAD) \ + || (CONFIG_KEYPAD == IPOD_4G_PAD) + {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, +#elif CONFIG_KEYPAD == LOGIK_DAX_PAD + {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY}, +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD + {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE}, +#elif CONFIG_KEYPAD == MEIZU_M6SL_PAD + {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU}, +#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \ + CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD + {PF_QUIT, BUTTON_OFF, BUTTON_NONE}, +#endif +#if CONFIG_KEYPAD == IAUDIO_M3_PAD + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE) +#else + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +#endif +}; +const struct button_mapping *pf_contexts[] = +{ + pf_context_album_scroll, + pf_context_buttons +}; + +#if LCD_DEPTH < 8 +#if LCD_DEPTH > 1 +#define N_BRIGHT(y) LCD_BRIGHTNESS(y) +#else /* LCD_DEPTH <= 1 */ +#define N_BRIGHT(y) ((y > 127) ? 0 : 1) +#ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */ +#define PICTUREFLOW_DRMODE DRMODE_SOLID +#else +#define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID) +#endif +#endif /* LCD_DEPTH <= 1 */ +#define USEGSLIB +GREY_INFO_STRUCT +#define LCD_BUF _grey_info.buffer +#define MYLCD(fn) grey_ ## fn +#define G_PIX(r,g,b) \ + (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256 +#define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b)) +#define G_BRIGHT(y) (y) +#define BUFFER_WIDTH _grey_info.width +#define BUFFER_HEIGHT _grey_info.height +typedef unsigned char pix_t; +#else /* LCD_DEPTH >= 8 */ +#define LCD_BUF rb->lcd_framebuffer +#define MYLCD(fn) rb->lcd_ ## fn +#define G_PIX LCD_RGBPACK +#define N_PIX LCD_RGBPACK +#define G_BRIGHT(y) LCD_RGBPACK(y,y,y) +#define N_BRIGHT(y) LCD_RGBPACK(y,y,y) +#define BUFFER_WIDTH LCD_WIDTH +#define BUFFER_HEIGHT LCD_HEIGHT +typedef fb_data pix_t; +#endif /* LCD_DEPTH >= 8 */ + +/* for fixed-point arithmetic, we need minimum 32-bit long + long long (64-bit) might be useful for multiplication and division */ +#define PFreal long +#define PFREAL_SHIFT 10 +#define PFREAL_FACTOR (1 << PFREAL_SHIFT) +#define PFREAL_ONE (1 << PFREAL_SHIFT) +#define PFREAL_HALF (PFREAL_ONE >> 1) + + +#define IANGLE_MAX 1024 +#define IANGLE_MASK 1023 + +#define REFLECT_TOP (LCD_HEIGHT * 2 / 3) +#define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP) +#define DISPLAY_HEIGHT REFLECT_TOP +#define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \ + LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5)) +#define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \ + (REFLECT_HEIGHT * 5)) +#define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT) +#define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120) +#define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT) +#define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF) +#define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF) + +#define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */ + +#define MAX_SLIDES_COUNT 10 + +#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200 +#define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow" + +#define EV_EXIT 9999 +#define EV_WAKEUP 1337 + +/* maximum number of albums */ + +#define MAX_TRACKS 50 +#define AVG_TRACK_NAME_LENGTH 20 + + +#define UNIQBUF_SIZE (64*1024) + +#define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw" +#define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp" + +/* Error return values */ +#define ERROR_NO_ALBUMS -1 +#define ERROR_BUFFER_FULL -2 + +/* current version for cover cache */ +#define CACHE_VERSION 2 +#define CONFIG_VERSION 1 +#define CONFIG_FILE "pictureflow.cfg" + +/** structs we use */ + +struct slide_data { + int slide_index; + int angle; + PFreal cx; + PFreal cy; + PFreal distance; +}; + +struct slide_cache { + int index; /* index of the cached slide */ + int hid; /* handle ID of the cached slide */ + short next; /* "next" slide, with LRU last */ + short prev; /* "previous" slide */ +}; + +struct album_data { + int name_idx; + long seek; +}; + +struct track_data { + int name_idx; + long seek; +}; + +struct rect { + int left; + int right; + int top; + int bottom; +}; + +struct load_slide_event_data { + int slide_index; + int cache_index; +}; + + +struct pfraw_header { + int32_t width; /* bmap width in pixels */ + int32_t height; /* bmap height in pixels */ +}; + +const struct picture logos[]={ + {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo}, +}; + +enum show_album_name_values { album_name_hide = 0, album_name_bottom, + album_name_top }; +static char* show_album_name_conf[] = +{ + "hide", + "bottom", + "top" +}; + +#define MAX_SPACING 40 +#define MAX_MARGIN 80 + +/* config values and their defaults */ +static int slide_spacing = DISPLAY_WIDTH / 4; +static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12; +static int num_slides = 4; +static int zoom = 100; +static bool show_fps = false; +static bool resize = true; +static int cache_version = 0; +static int show_album_name = (LCD_HEIGHT > 100) + ? album_name_top : album_name_bottom; + +static struct configdata config[] = +{ + { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing", + NULL }, + { TYPE_INT, 0, MAX_MARGIN, { .int_p = ¢er_margin }, "center margin", + NULL }, + { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count", + NULL }, + { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL }, + { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL }, + { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL }, + { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL }, + { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name", + show_album_name_conf } +}; + +#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata)) + +/** below we allocate the memory we want to use **/ + +static pix_t *buffer; /* for now it always points to the lcd framebuffer */ +static uint8_t reflect_table[REFLECT_HEIGHT]; +static struct slide_data center_slide; +static struct slide_data left_slides[MAX_SLIDES_COUNT]; +static struct slide_data right_slides[MAX_SLIDES_COUNT]; +static int slide_frame; +static int step; +static int target; +static int fade; +static int center_index = 0; /* index of the slide that is in the center */ +static int itilt; +static PFreal offsetX; +static PFreal offsetY; +static int number_of_slides; + +static struct slide_cache cache[SLIDE_CACHE_SIZE]; +static int cache_free; +static int cache_used = -1; +static int cache_left_index = -1; +static int cache_right_index = -1; +static int cache_center_index = -1; + +/* use long for aligning */ +unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)]; +/* queue (as array) for scheduling load_surface */ + +static int empty_slide_hid; + +unsigned int thread_id; +struct event_queue thread_q; + +static struct tagcache_search tcs; + +static struct buflib_context buf_ctx; + +static struct album_data *album; +static char *album_names; +static int album_count; + +static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH]; +static struct track_data tracks[MAX_TRACKS]; +static int track_count; +static int track_index; +static int selected_track; +static int selected_track_pulse; +void reset_track_list(void); + +void * buf; +size_t buf_size; + +static bool thread_is_running; + +static int cover_animation_keyframe; +static int extra_fade; + +static int albumtxt_x = 0; +static int albumtxt_dir = -1; +static int prev_center_index = -1; + +static int start_index_track_list = 0; +static int track_list_visible_entries = 0; +static int track_list_y; +static int track_list_h; +static int track_scroll_index = 0; +static int track_scroll_dir = 1; + +/* + Proposals for transitions: + + pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed + -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked + + pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released + + pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed + + TODO: + pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed + pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed + + pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED +*/ +enum pf_states { + pf_idle = 0, + pf_scrolling, + pf_cover_in, + pf_show_tracks, + pf_cover_out +}; + +static int pf_state; + +/** code */ +static inline unsigned fade_color(pix_t c, unsigned a); +bool save_pfraw(char* filename, struct bitmap *bm); +bool load_new_slide(void); +int load_surface(int); + +static inline PFreal fmul(PFreal a, PFreal b) +{ + return (a*b) >> PFREAL_SHIFT; +} + +/** + * This version preshifts each operand, which is useful when we know how many + * of the least significant bits will be empty, or are worried about overflow + * in a particular calculation + */ +static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2) +{ + return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2); +} + +/* ARMv5+ has a clz instruction equivalent to our function. + */ +#if (defined(CPU_ARM) && (ARM_ARCH > 4)) +static inline int clz(uint32_t v) +{ + return __builtin_clz(v); +} + +/* Otherwise, use our clz, which can be inlined */ +#elif defined(CPU_COLDFIRE) +/* This clz is based on the log2(n) implementation at + * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog + * A clz benchmark plugin showed this to be about 14% faster on coldfire + * than the LUT-based version. + */ +static inline int clz(uint32_t v) +{ + int r = 32; + if (v >= 0x10000) + { + v >>= 16; + r -= 16; + } + if (v & 0xff00) + { + v >>= 8; + r -= 8; + } + if (v & 0xf0) + { + v >>= 4; + r -= 4; + } + if (v & 0xc) + { + v >>= 2; + r -= 2; + } + if (v & 2) + { + v >>= 1; + r -= 1; + } + r -= v; + return r; +} +#else +static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0 }; +/* This clz is based on the log2(n) implementation at + * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup + * It is not any faster than the one above, but trades 16B in the lookup table + * for a savings of 12B per each inlined call. + */ +static inline int clz(uint32_t v) +{ + int r = 28; + if (v >= 0x10000) + { + v >>= 16; + r -= 16; + } + if (v & 0xff00) + { + v >>= 8; + r -= 8; + } + if (v & 0xf0) + { + v >>= 4; + r -= 4; + } + return r + clz_lut[v]; +} +#endif + +/* Return the maximum possible left shift for a signed int32, without + * overflow + */ +static inline int allowed_shift(int32_t val) +{ + uint32_t uval = val ^ (val >> 31); + return clz(uval) - 1; +} + +/* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting + * num and den before dividing. + */ +static inline PFreal fdiv(PFreal num, PFreal den) +{ + int shift = allowed_shift(num); + shift = MIN(PFREAL_SHIFT, shift); + num <<= shift; + den >>= PFREAL_SHIFT - shift; + return num / den; +} + +#define fmin(a,b) (((a) < (b)) ? (a) : (b)) +#define fmax(a,b) (((a) > (b)) ? (a) : (b)) +#define fabs(a) (a < 0 ? -a : a) +#define fbound(min,val,max) (fmax((min),fmin((max),(val)))) + +#if CONFIG_CPU == SH7034 +/* 16*16->32 bit multiplication is a single instrcution on the SH1 */ +#define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b)))) +#else +#define MULUQ(a, b) ((a) * (b)) +#endif + + +#if 0 +#define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT ) +#define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m ) + +#define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2))) +#define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) ) + +static inline PFreal fmul(PFreal a, PFreal b) +{ + return (a*b) >> PFREAL_SHIFT; +} + +static inline PFreal fdiv(PFreal n, PFreal m) +{ + return (n<<(PFREAL_SHIFT))/m; +} +#endif + +/* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */ +static const short sin_tab[] = { + 0, 100, 200, 297, 392, 483, 569, 650, + 724, 792, 851, 903, 946, 980, 1004, 1019, + 1024, 1019, 1004, 980, 946, 903, 851, 792, + 724, 650, 569, 483, 392, 297, 200, 100, + 0, -100, -200, -297, -392, -483, -569, -650, + -724, -792, -851, -903, -946, -980, -1004, -1019, + -1024, -1019, -1004, -980, -946, -903, -851, -792, + -724, -650, -569, -483, -392, -297, -200, -100, + 0 +}; + +static inline PFreal fsin(int iangle) +{ + iangle &= IANGLE_MASK; + + int i = (iangle >> 4); + PFreal p = sin_tab[i]; + PFreal q = sin_tab[(i+1)]; + PFreal g = (q - p); + return p + g * (iangle-i*16)/16; +} + +static inline PFreal fcos(int iangle) +{ + return fsin(iangle + (IANGLE_MAX >> 2)); +} + +static inline uint32_t div255(uint32_t val) +{ + return ((((val >> 8) + val) >> 8) + val) >> 8; +} + +#define SCALE_VAL(val,out) div255((val) * (out) + 127) + +static void output_row_transposed(uint32_t row, void * row_in, + struct scaler_context *ctx) +{ + pix_t *dest = (pix_t*)ctx->bm->data + row; + pix_t *end = dest + ctx->bm->height * ctx->bm->width; +#ifdef USEGSLIB + uint32_t *qp = (uint32_t*)row_in; + for (; dest < end; dest += ctx->bm->height) + *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor); +#else + struct uint32_rgb *qp = (struct uint32_rgb*)row_in; + uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31), + rb_rnd = SCALE_VAL(ctx->round, 31), + g_mul = SCALE_VAL(ctx->divisor, 63), + g_rnd = SCALE_VAL(ctx->round, 63); + int r, g, b; + for (; dest < end; dest += ctx->bm->height) + { + r = SC_MUL(qp->r + rb_rnd, rb_mul); + g = SC_MUL(qp->g + g_rnd, g_mul); + b = SC_MUL(qp->b + rb_rnd, rb_mul); + qp++; + *dest = LCD_RGBPACK_LCD(r,g,b); + } +#endif +} + +#ifdef HAVE_LCD_COLOR +static void output_row_transposed_fromyuv(uint32_t row, void * row_in, + struct scaler_context *ctx) +{ + pix_t *dest = (pix_t*)ctx->bm->data + row; + pix_t *end = dest + ctx->bm->height * ctx->bm->width; + struct uint32_rgb *qp = (struct uint32_rgb*)row_in; + for (; dest < end; dest += ctx->bm->height) + { + unsigned r, g, b, y, u, v; + y = SC_MUL(qp->b + ctx->round, ctx->divisor); + u = SC_MUL(qp->g + ctx->round, ctx->divisor); + v = SC_MUL(qp->r + ctx->round, ctx->divisor); + qp++; + yuv_to_rgb(y, u, v, &r, &g, &b); + r = (31 * r + (r >> 3) + 127) >> 8; + g = (63 * g + (g >> 2) + 127) >> 8; + b = (31 * b + (b >> 3) + 127) >> 8; + *dest = LCD_RGBPACK_LCD(r, g, b); + } +} +#endif + +static unsigned int get_size(struct bitmap *bm) +{ + return bm->width * bm->height * sizeof(pix_t); +} + +const struct custom_format format_transposed = { +#ifdef HAVE_LCD_COLOR + .output_row = { + output_row_transposed, + output_row_transposed_fromyuv + }, +#else + .output_row = output_row_transposed, +#endif + .get_size = get_size +}; + +static const struct button_mapping* get_context_map(int context) +{ + return pf_contexts[context & ~CONTEXT_CUSTOM]; +} + +/* Create the lookup table with the scaling values for the reflections */ +void init_reflect_table(void) +{ + int i; + for (i = 0; i < REFLECT_HEIGHT; i++) + reflect_table[i] = + (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) / + (5 * REFLECT_HEIGHT); +} + +/** + Create an index of all albums from the database. + Also store the album names so we can access them later. + */ +int create_album_index(void) +{ + buf_size -= UNIQBUF_SIZE * sizeof(long); + long *uniqbuf = (long *)(buf_size + (char *)buf); + album = ((struct album_data *)uniqbuf) - 1; + rb->memset(&tcs, 0, sizeof(struct tagcache_search) ); + album_count = 0; + rb->tagcache_search(&tcs, tag_album); + rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE); + unsigned int l, old_l = 0; + album_names = buf; + album[0].name_idx = 0; + while (rb->tagcache_get_next(&tcs)) + { + buf_size -= sizeof(struct album_data); + l = rb->strlen(tcs.result) + 1; + if ( album_count > 0 ) + album[-album_count].name_idx = album[1-album_count].name_idx + old_l; + + if ( l > buf_size ) + /* not enough memory */ + return ERROR_BUFFER_FULL; + + rb->strcpy(buf, tcs.result); + buf_size -= l; + buf = l + (char *)buf; + album[-album_count].seek = tcs.result_seek; + old_l = l; + album_count++; + } + rb->tagcache_search_finish(&tcs); + ALIGN_BUFFER(buf, buf_size, 4); + int i; + struct album_data* tmp_album = (struct album_data*)buf; + for (i = album_count - 1; i >= 0; i--) + tmp_album[i] = album[-i]; + album = tmp_album; + buf = album + album_count; + buf_size += UNIQBUF_SIZE * sizeof(long); + return (album_count > 0) ? 0 : ERROR_NO_ALBUMS; +} + +/** + Return a pointer to the album name of the given slide_index + */ +char* get_album_name(const int slide_index) +{ + return album_names + album[slide_index].name_idx; +} + +/** + Return a pointer to the track name of the active album + create_track_index has to be called first. + */ +char* get_track_name(const int track_index) +{ + if ( track_index < track_count ) + return track_names + tracks[track_index].name_idx; + return 0; +} + +/** + Create the track index of the given slide_index. + */ +int create_track_index(const int slide_index) +{ + if ( slide_index == track_index ) { + return -1; + } + + if (!rb->tagcache_search(&tcs, tag_title)) + return -1; + + int ret = 0; + char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4]; + int temp_seeks[MAX_TRACKS]; + + rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek); + track_count=0; + int string_index = 0; + int l, track_num, heighest_index = 0; + + for(l=0;ltagcache_get_next(&tcs) && track_count < MAX_TRACKS) + { + track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1; + if (track_num >= 0) + { + rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]), + "%d: %s", track_num+1, tcs.result); + temp_seeks[track_num] = tcs.result_seek; + } + else + { + track_num = 0; + while (temp_titles[track_num][0] != '\0') + track_num++; + rb->strcpy(temp_titles[track_num], tcs.result); + temp_seeks[track_num] = tcs.result_seek; + } + if (track_num > heighest_index) + heighest_index = track_num; + track_count++; + } + + rb->tagcache_search_finish(&tcs); + track_index = slide_index; + + /* now fix the track list order */ + l = 0; + track_count = 0; + while (l <= heighest_index && + string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH) + { + if (temp_titles[l][0] != '\0') + { + rb->strcpy(track_names + string_index, temp_titles[l]); + tracks[track_count].name_idx = string_index; + tracks[track_count].seek = temp_seeks[l]; + string_index += rb->strlen(temp_titles[l]) + 1; + track_count++; + } + l++; + } + if (ret != 0) + return ret; + else + return (track_count > 0) ? 0 : -1; +} + +/** + Determine filename of the album art for the given slide_index and + store the result in buf. + The algorithm looks for the first track of the given album uses + find_albumart to find the filename. + */ +bool get_albumart_for_index_from_db(const int slide_index, char *buf, + int buflen) +{ + if ( slide_index == -1 ) + { + rb->strncpy( buf, EMPTY_SLIDE, buflen ); + } + + if (!rb->tagcache_search(&tcs, tag_filename)) + return false; + + bool result; + /* find the first track of the album */ + rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek); + + if ( rb->tagcache_get_next(&tcs) ) { + struct mp3entry id3; + int fd; + + fd = rb->open(tcs.result, O_RDONLY); + rb->get_metadata(&id3, fd, tcs.result); + rb->close(fd); + if ( search_albumart_files(&id3, "", buf, buflen) ) + result = true; + else + result = false; + } + else { + /* did not find a matching track */ + result = false; + } + rb->tagcache_search_finish(&tcs); + return result; +} + +/** + Draw the PictureFlow logo + */ +void draw_splashscreen(void) +{ + struct screen* display = rb->screens[0]; + const struct picture* logo = &(logos[display->screen_type]); + +#if LCD_DEPTH > 1 + rb->lcd_set_background(N_BRIGHT(0)); + rb->lcd_set_foreground(N_BRIGHT(255)); +#else + rb->lcd_set_drawmode(PICTUREFLOW_DRMODE); +#endif + rb->lcd_clear_display(); + +#if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */ + rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID); + picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10); + rb->lcd_set_drawmode(PICTUREFLOW_DRMODE); +#else + picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10); +#endif + + rb->lcd_update(); +} + + +/** + Draw a simple progress bar + */ +void draw_progressbar(int step) +{ + int txt_w, txt_h; + const int bar_height = 22; + const int w = LCD_WIDTH - 20; + const int x = 10; + + rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h); + + int y = (LCD_HEIGHT - txt_h)/2; + + rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork"); + y += (txt_h + 5); + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground(N_BRIGHT(100)); +#endif + rb->lcd_drawrect(x, y, w+2, bar_height); +#if LCD_DEPTH > 1 + rb->lcd_set_foreground(N_PIX(165, 231, 82)); +#endif + + rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2); +#if LCD_DEPTH > 1 + rb->lcd_set_foreground(N_BRIGHT(255)); +#endif + rb->lcd_update(); + rb->yield(); +} + +/** + Precomupte the album art images and store them in CACHE_PREFIX. + */ +bool create_albumart_cache(void) +{ + int ret; + + int i, slides = 0; + struct bitmap input_bmp; + + char pfraw_file[MAX_PATH]; + char albumart_file[MAX_PATH]; + unsigned int format = FORMAT_NATIVE; + cache_version = 0; + configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); + if (resize) + format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT; + for (i=0; i < album_count; i++) + { + rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw", + i); + /* delete existing cache, so it's a true rebuild */ + if(rb->file_exists(pfraw_file)) + rb->remove(pfraw_file); + draw_progressbar(i); + if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH)) + continue; + + input_bmp.data = buf; + input_bmp.width = DISPLAY_WIDTH; + input_bmp.height = DISPLAY_HEIGHT; + ret = read_image_file(albumart_file, &input_bmp, + buf_size, format, &format_transposed); + if (ret <= 0) { + rb->splash(HZ, "Could not read bmp"); + continue; /* skip missing/broken files */ + } + if (!save_pfraw(pfraw_file, &input_bmp)) + { + rb->splash(HZ, "Could not write bmp"); + } + slides++; + if ( rb->button_get(false) == PF_MENU ) return false; + } + if ( slides == 0 ) { + /* Warn the user that we couldn't find any albumart */ + rb->splash(2*HZ, "No album art found"); + return false; + } + return true; +} + +/** + Thread used for loading and preparing bitmaps in the background + */ +void thread(void) +{ + long sleep_time = 5 * HZ; + struct queue_event ev; + while (1) { + rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time); + switch (ev.id) { + case EV_EXIT: + return; + case EV_WAKEUP: + /* we just woke up */ + break; + } + while ( load_new_slide() ) { + rb->yield(); + switch (ev.id) { + case EV_EXIT: + return; + } + } + } +} + + +/** + End the thread by posting the EV_EXIT event + */ +void end_pf_thread(void) +{ + if ( thread_is_running ) { + rb->queue_post(&thread_q, EV_EXIT, 0); + rb->thread_wait(thread_id); + /* remove the thread's queue from the broadcast list */ + rb->queue_delete(&thread_q); + thread_is_running = false; + } + +} + + +/** + Create the thread an setup the event queue + */ +bool create_pf_thread(void) +{ + /* put the thread's queue in the bcast list */ + rb->queue_init(&thread_q, true); + if ((thread_id = rb->create_thread( + thread, + thread_stack, + sizeof(thread_stack), + 0, + "Picture load thread" + IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2, + PRIORITY_REALTIME + 1)) + IF_COP(, CPU) + ) + ) == 0) { + return false; + } + thread_is_running = true; + rb->queue_post(&thread_q, EV_WAKEUP, 0); + return true; +} + +/** + Safe the given bitmap as filename in the pfraw format + */ +bool save_pfraw(char* filename, struct bitmap *bm) +{ + struct pfraw_header bmph; + bmph.width = bm->width; + bmph.height = bm->height; + int fh = rb->creat( filename ); + if( fh < 0 ) return false; + rb->write( fh, &bmph, sizeof( struct pfraw_header ) ); + int y; + for( y = 0; y < bm->height; y++ ) + { + pix_t *d = (pix_t*)( bm->data ) + (y*bm->width); + rb->write( fh, d, sizeof( pix_t ) * bm->width ); + } + rb->close( fh ); + return true; +} + + +/* + * The following functions implement the linked-list-in-array used to manage + * the LRU cache of slides, and the list of free cache slots. + */ + +#define seek_right_while(start, cond) \ +({ \ + int ind_, next_ = (start); \ + do { \ + ind_ = next_; \ + next_ = cache[ind_].next; \ + } while (next_ != cache_used && (cond)); \ + ind_; \ +}) + +#define seek_left_while(start, cond) \ +({ \ + int ind_, next_ = (start); \ + do { \ + ind_ = next_; \ + next_ = cache[ind_].prev; \ + } while (ind_ != cache_used && (cond)); \ + ind_; \ +}) + +/** + Pop the given item from the linked list starting at *head, returning the next + item, or -1 if the list is now empty. +*/ +static inline int lla_pop_item (int *head, int i) +{ + int prev = cache[i].prev; + int next = cache[i].next; + if (i == next) + { + *head = -1; + return -1; + } + else if (i == *head) + *head = next; + cache[next].prev = prev; + cache[prev].next = next; + return next; +} + + +/** + Pop the head item from the list starting at *head, returning the index of the + item, or -1 if the list is already empty. +*/ +static inline int lla_pop_head (int *head) +{ + int i = *head; + if (i != -1) + lla_pop_item(head, i); + return i; +} + +/** + Insert the item at index i before the one at index p. +*/ +static inline void lla_insert (int i, int p) +{ + int next = p; + int prev = cache[next].prev; + cache[next].prev = i; + cache[prev].next = i; + cache[i].next = next; + cache[i].prev = prev; +} + + +/** + Insert the item at index i at the end of the list starting at *head. +*/ +static inline void lla_insert_tail (int *head, int i) +{ + if (*head == -1) + { + *head = i; + cache[i].next = i; + cache[i].prev = i; + } else + lla_insert(i, *head); +} + +/** + Insert the item at index i before the one at index p. +*/ +static inline void lla_insert_after(int i, int p) +{ + p = cache[p].next; + lla_insert(i, p); +} + + +/** + Insert the item at index i before the one at index p in the list starting at + *head +*/ +static inline void lla_insert_before(int *head, int i, int p) +{ + lla_insert(i, p); + if (*head == p) + *head = i; +} + + +/** + Free the used slide at index i, and its buffer, and move it to the free + slides list. +*/ +static inline void free_slide(int i) +{ + if (cache[i].hid != empty_slide_hid) + buflib_free(&buf_ctx, cache[i].hid); + cache[i].index = -1; + lla_pop_item(&cache_used, i); + lla_insert_tail(&cache_free, i); + if (cache_used == -1) + { + cache_right_index = -1; + cache_left_index = -1; + cache_center_index = -1; + } +} + + +/** + Free one slide ranked above the given priority. If no such slide can be found, + return false. +*/ +static inline int free_slide_prio(int prio) +{ + if (cache_used == -1) + return false; + int i, l = cache_used, r = cache[cache_used].prev, prio_max; + int prio_l = cache[l].index < center_index ? + center_index - cache[l].index : 0; + int prio_r = cache[r].index > center_index ? + cache[r].index - center_index : 0; + if (prio_l > prio_r) + { + i = l; + prio_max = prio_l; + } else { + i = r; + prio_max = prio_r; + } + if (prio_max > prio) + { + if (i == cache_left_index) + cache_left_index = cache[i].next; + if (i == cache_right_index) + cache_right_index = cache[i].prev; + free_slide(i); + return true; + } else + return false; +} + +/** + Read the pfraw image given as filename and return the hid of the buffer + */ +int read_pfraw(char* filename, int prio) +{ + struct pfraw_header bmph; + int fh = rb->open(filename, O_RDONLY); + if( fh < 0 ) + return empty_slide_hid; + else + rb->read(fh, &bmph, sizeof(struct pfraw_header)); + + int size = sizeof(struct bitmap) + sizeof( pix_t ) * + bmph.width * bmph.height; + + int hid; + while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio)); + + if (!hid) { + rb->close( fh ); + return 0; + } + + struct dim *bm = buflib_get_data(&buf_ctx, hid); + + bm->width = bmph.width; + bm->height = bmph.height; + pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm); + + int y; + for( y = 0; y < bm->height; y++ ) + { + rb->read( fh, data , sizeof( pix_t ) * bm->width ); + data += bm->width; + } + rb->close( fh ); + return hid; +} + + +/** + Load the surface for the given slide_index into the cache at cache_index. + */ +static inline bool load_and_prepare_surface(const int slide_index, + const int cache_index, + const int prio) +{ + char tmp_path_name[MAX_PATH+1]; + rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw", + slide_index); + + int hid = read_pfraw(tmp_path_name, prio); + if (!hid) + return false; + + cache[cache_index].hid = hid; + + if ( cache_index < SLIDE_CACHE_SIZE ) { + cache[cache_index].index = slide_index; + } + + return true; +} + + +/** + Load the "next" slide that we can load, freeing old slides if needed, provided + that they are further from center_index than the current slide +*/ +bool load_new_slide(void) +{ + int i = -1; + if (cache_center_index != -1) + { + int next, prev; + if (cache[cache_center_index].index != center_index) + { + if (cache[cache_center_index].index < center_index) + { + cache_center_index = seek_right_while(cache_center_index, + cache[next_].index <= center_index); + prev = cache_center_index; + next = cache[cache_center_index].next; + } + else + { + cache_center_index = seek_left_while(cache_center_index, + cache[next_].index >= center_index); + next = cache_center_index; + prev = cache[cache_center_index].prev; + } + if (cache[cache_center_index].index != center_index) + { + if (cache_free == -1) + free_slide_prio(0); + i = lla_pop_head(&cache_free); + if (!load_and_prepare_surface(center_index, i, 0)) + goto fail_and_refree; + if (cache[next].index == -1) + { + if (cache[prev].index == -1) + goto insert_first_slide; + else + next = cache[prev].next; + } + lla_insert(i, next); + if (cache[i].index < cache[cache_used].index) + cache_used = i; + cache_center_index = i; + cache_left_index = i; + cache_right_index = i; + return true; + } + } + if (cache[cache_left_index].index > + cache[cache_center_index].index) + cache_left_index = cache_center_index; + if (cache[cache_right_index].index < + cache[cache_center_index].index) + cache_right_index = cache_center_index; + cache_left_index = seek_left_while(cache_left_index, + cache[ind_].index - 1 == cache[next_].index); + cache_right_index = seek_right_while(cache_right_index, + cache[ind_].index - 1 == cache[next_].index); + int prio_l = cache[cache_center_index].index - + cache[cache_left_index].index + 1; + int prio_r = cache[cache_right_index].index - + cache[cache_center_index].index + 1; + if ((prio_l < prio_r || + cache[cache_right_index].index >= number_of_slides) && + cache[cache_left_index].index > 0) + { + if (cache_free == -1 && !free_slide_prio(prio_l)) + return false; + i = lla_pop_head(&cache_free); + if (load_and_prepare_surface(cache[cache_left_index].index + - 1, i, prio_l)) + { + lla_insert_before(&cache_used, i, cache_left_index); + cache_left_index = i; + return true; + } + } else if(cache[cache_right_index].index < number_of_slides - 1) + { + if (cache_free == -1 && !free_slide_prio(prio_r)) + return false; + i = lla_pop_head(&cache_free); + if (load_and_prepare_surface(cache[cache_right_index].index + + 1, i, prio_r)) + { + lla_insert_after(i, cache_right_index); + cache_right_index = i; + return true; + } + } + } else { + i = lla_pop_head(&cache_free); + if (load_and_prepare_surface(center_index, i, 0)) + { +insert_first_slide: + cache[i].next = i; + cache[i].prev = i; + cache_center_index = i; + cache_left_index = i; + cache_right_index = i; + cache_used = i; + return true; + } + } +fail_and_refree: + if (i != -1) + { + lla_insert_tail(&cache_free, i); + } + return false; +} + + +/** + Get a slide from the buffer + */ +static inline struct dim *get_slide(const int hid) +{ + if (!hid) + return NULL; + + struct dim *bmp; + + bmp = buflib_get_data(&buf_ctx, hid); + + return bmp; +} + + +/** + Return the requested surface +*/ +static inline struct dim *surface(const int slide_index) +{ + if (slide_index < 0) + return 0; + if (slide_index >= number_of_slides) + return 0; + int i; + if ((i = cache_used ) != -1) + { + do { + if (cache[i].index == slide_index) + return get_slide(cache[i].hid); + i = cache[i].next; + } while (i != cache_used); + } + return get_slide(empty_slide_hid); +} + +/** + adjust slides so that they are in "steady state" position + */ +void reset_slides(void) +{ + center_slide.angle = 0; + center_slide.cx = 0; + center_slide.cy = 0; + center_slide.distance = 0; + center_slide.slide_index = center_index; + + int i; + for (i = 0; i < num_slides; i++) { + struct slide_data *si = &left_slides[i]; + si->angle = itilt; + si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE); + si->cy = offsetY; + si->slide_index = center_index - 1 - i; + si->distance = 0; + } + + for (i = 0; i < num_slides; i++) { + struct slide_data *si = &right_slides[i]; + si->angle = -itilt; + si->cx = offsetX + slide_spacing * i * PFREAL_ONE; + si->cy = offsetY; + si->slide_index = center_index + 1 + i; + si->distance = 0; + } +} + + +/** + Updates look-up table and other stuff necessary for the rendering. + Call this when the viewport size or slide dimension is changed. + * + * To calculate the offset that will provide the proper margin, we use the same + * projection used to render the slides. The solution for xc, the slide center, + * is: + * xp * (zo + xs * sin(r)) + * xc = xp - xs * cos(r) + ─────────────────────── + * z + * TODO: support moving the side slides toward or away from the camera + */ +void recalc_offsets(void) +{ + PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF; + PFreal zo; + PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin * + PFREAL_ONE) * zoom / 100; + PFreal cosr, sinr; + + itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */ + cosr = fcos(-itilt); + sinr = fsin(-itilt); + zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R + + fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0); + offsetX = xp - fmul(xs, cosr) + fmuln(xp, + zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0) + / CAM_DIST; + offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2); +} + + +/** + Fade the given color by spreading the fb_data (ushort) + to an uint, multiply and compress the result back to a ushort. + */ +#if (LCD_PIXELFORMAT == RGB565SWAPPED) +static inline unsigned fade_color(pix_t c, unsigned a) +{ + unsigned int result; + c = swap16(c); + a = (a + 2) & 0x1fc; + result = ((c & 0xf81f) * a) & 0xf81f00; + result |= ((c & 0x7e0) * a) & 0x7e000; + result >>= 8; + return swap16(result); +} +#elif LCD_PIXELFORMAT == RGB565 +static inline unsigned fade_color(pix_t c, unsigned a) +{ + unsigned int result; + a = (a + 2) & 0x1fc; + result = ((c & 0xf81f) * a) & 0xf81f00; + result |= ((c & 0x7e0) * a) & 0x7e000; + result >>= 8; + return result; +} +#else +static inline unsigned fade_color(pix_t c, unsigned a) +{ + unsigned val = c; + return MULUQ(val, a) >> 8; +} +#endif + +/** + * Render a single slide + * Where xc is the slide's horizontal offset from center, xs is the horizontal + * on the slide from its center, zo is the slide's depth offset from the plane + * of the display, r is the angle at which the slide is tilted, and xp is the + * point on the display corresponding to xs on the slide, the projection + * formulas are: + * + * z * (xc + xs * cos(r)) + * xp = ────────────────────── + * z + zo + xs * sin(r) + * + * z * (xc - xp) - xp * zo + * xs = ──────────────────────── + * xp * sin(r) - z * cos(r) + * + * We use the xp projection once, to find the left edge of the slide on the + * display. From there, we use the xs reverse projection to find the horizontal + * offset from the slide center of each column on the screen, until we reach + * the right edge of the slide, or the screen. The reverse projection can be + * optimized by saving the numerator and denominator of the fraction, which can + * then be incremented by (z + zo) and sin(r) respectively. + */ +void render_slide(struct slide_data *slide, const int alpha) +{ + struct dim *bmp = surface(slide->slide_index); + if (!bmp) { + return; + } + if (slide->angle > 255 || slide->angle < -255) + return; + pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp); + + const int sw = bmp->width; + const int sh = bmp->height; + const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF; + const int w = LCD_WIDTH; + + uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */ + + if (alpha == 256) { /* opaque -> copy table */ + rb->memcpy(reftab, reflect_table, sizeof(reftab)); + } else { /* precalculate faded table */ + int i, lalpha; + for (i = 0; i < REFLECT_HEIGHT; i++) { + lalpha = reflect_table[i]; + reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8; + } + } + + PFreal cosr = fcos(slide->angle); + PFreal sinr = fsin(slide->angle); + PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom + - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0); + PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni; + PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)), + (CAM_DIST_R + zo + fmul(xs,sinr))); + + /* Since we're finding the screen position of the left edge of the slide, + * we round up. + */ + int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1) + >> PFREAL_SHIFT; + xp = DISPLAY_LEFT_R + xi * PFREAL_ONE; + if (xi >= w) { + return; + } + xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0); + xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr; + xs = fdiv(xsnum, xsden); + + xsnumi = -CAM_DIST_R - zo; + xsdeni = sinr; + int x; + int dy = PFREAL_ONE; + for (x = xi; x < w; x++) { + int column = (xs - slide_left) / PFREAL_ONE; + if (column >= sw) + break; + if (zo || slide->angle) + dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST; + + const pix_t *ptr = &src[column * bmp->height]; + const int pixelstep = BUFFER_WIDTH; + + int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE; + int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy); + pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x]; + + if (alpha == 256) { + while (p >= plim) { + *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT]; + p -= dy; + pixel -= pixelstep; + } + } else { + while (p >= plim) { + *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha); + p -= dy; + pixel -= pixelstep; + } + } + p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE; + plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy); + int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE, + p + (LCD_HEIGHT/2) * dy); + pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x]; + + if (alpha == 256) { + while (p < plim) { + *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT]; + p += dy; + pixel += pixelstep; + } + } else { + while (p < plim) { + *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha); + p += dy; + pixel += pixelstep; + } + } + while (p < plim2) { + int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh; + int lalpha = reftab[ty]; + *pixel = fade_color(ptr[sh - 1 - ty], lalpha); + p += dy; + pixel += pixelstep; + } + + if (zo || slide->angle) + { + xsnum += xsnumi; + xsden += xsdeni; + xs = fdiv(xsnum, xsden); + } else + xs += PFREAL_ONE; + + } + /* let the music play... */ + rb->yield(); + return; +} + + +/** + Jump the the given slide_index + */ +static inline void set_current_slide(const int slide_index) +{ + int old_center_index = center_index; + step = 0; + center_index = fbound(slide_index, 0, number_of_slides - 1); + if (old_center_index != center_index) + rb->queue_post(&thread_q, EV_WAKEUP, 0); + target = center_index; + slide_frame = slide_index << 16; + reset_slides(); +} + +/** + Start the animation for changing slides + */ +void start_animation(void) +{ + step = (target < center_slide.slide_index) ? -1 : 1; + pf_state = pf_scrolling; +} + +/** + Go to the previous slide + */ +void show_previous_slide(void) +{ + if (step == 0) { + if (center_index > 0) { + target = center_index - 1; + start_animation(); + } + } else if ( step > 0 ) { + target = center_index; + start_animation(); + } else { + target = fmax(0, center_index - 2); + } +} + + +/** + Go to the next slide + */ +void show_next_slide(void) +{ + if (step == 0) { + if (center_index < number_of_slides - 1) { + target = center_index + 1; + start_animation(); + } + } else if ( step < 0 ) { + target = center_index; + start_animation(); + } else { + target = fmin(center_index + 2, number_of_slides - 1); + } +} + + +/** + Render the slides. Updates only the offscreen buffer. +*/ +void render_all_slides(void) +{ + MYLCD(set_background)(G_BRIGHT(0)); + /* TODO: Optimizes this by e.g. invalidating rects */ + MYLCD(clear_display)(); + + int nleft = num_slides; + int nright = num_slides; + + int index; + if (step == 0) { + /* no animation, boring plain rendering */ + for (index = nleft - 2; index >= 0; index--) { + int alpha = (index < nleft - 2) ? 256 : 128; + alpha -= extra_fade; + if (alpha > 0 ) + render_slide(&left_slides[index], alpha); + } + for (index = nright - 2; index >= 0; index--) { + int alpha = (index < nright - 2) ? 256 : 128; + alpha -= extra_fade; + if (alpha > 0 ) + render_slide(&right_slides[index], alpha); + } + } else { + /* the first and last slide must fade in/fade out */ + for (index = nleft - 1; index >= 0; index--) { + int alpha = 256; + if (index == nleft - 1) + alpha = (step > 0) ? 0 : 128 - fade / 2; + if (index == nleft - 2) + alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2; + if (index == nleft - 3) + alpha = (step > 0) ? 256 - fade / 2 : 256; + render_slide(&left_slides[index], alpha); + } + for (index = nright - 1; index >= 0; index--) { + int alpha = (index < nright - 2) ? 256 : 128; + if (index == nright - 1) + alpha = (step > 0) ? fade / 2 : 0; + if (index == nright - 2) + alpha = (step > 0) ? 128 + fade / 2 : fade / 2; + if (index == nright - 3) + alpha = (step > 0) ? 256 : 128 + fade / 2; + render_slide(&right_slides[index], alpha); + } + } + render_slide(¢er_slide, 256); +} + + +/** + Updates the animation effect. Call this periodically from a timer. +*/ +void update_scroll_animation(void) +{ + if (step == 0) + return; + + int speed = 16384; + int i; + + /* deaccelerate when approaching the target */ + if (true) { + const int max = 2 * 65536; + + int fi = slide_frame; + fi -= (target << 16); + if (fi < 0) + fi = -fi; + fi = fmin(fi, max); + + int ia = IANGLE_MAX * (fi - max / 2) / (max * 2); + speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE; + } + + slide_frame += speed * step; + + int index = slide_frame >> 16; + int pos = slide_frame & 0xffff; + int neg = 65536 - pos; + int tick = (step < 0) ? neg : pos; + PFreal ftick = (tick * PFREAL_ONE) >> 16; + + /* the leftmost and rightmost slide must fade away */ + fade = pos / 256; + + if (step < 0) + index++; + if (center_index != index) { + center_index = index; + rb->queue_post(&thread_q, EV_WAKEUP, 0); + slide_frame = index << 16; + center_slide.slide_index = center_index; + for (i = 0; i < num_slides; i++) + left_slides[i].slide_index = center_index - 1 - i; + for (i = 0; i < num_slides; i++) + right_slides[i].slide_index = center_index + 1 + i; + } + + center_slide.angle = (step * tick * itilt) >> 16; + center_slide.cx = -step * fmul(offsetX, ftick); + center_slide.cy = fmul(offsetY, ftick); + + if (center_index == target) { + reset_slides(); + pf_state = pf_idle; + step = 0; + fade = 256; + return; + } + + for (i = 0; i < num_slides; i++) { + struct slide_data *si = &left_slides[i]; + si->angle = itilt; + si->cx = + -(offsetX + slide_spacing * i * PFREAL_ONE + step + * slide_spacing * ftick); + si->cy = offsetY; + } + + for (i = 0; i < num_slides; i++) { + struct slide_data *si = &right_slides[i]; + si->angle = -itilt; + si->cx = + offsetX + slide_spacing * i * PFREAL_ONE - step + * slide_spacing * ftick; + si->cy = offsetY; + } + + if (step > 0) { + PFreal ftick = (neg * PFREAL_ONE) >> 16; + right_slides[0].angle = -(neg * itilt) >> 16; + right_slides[0].cx = fmul(offsetX, ftick); + right_slides[0].cy = fmul(offsetY, ftick); + } else { + PFreal ftick = (pos * PFREAL_ONE) >> 16; + left_slides[0].angle = (pos * itilt) >> 16; + left_slides[0].cx = -fmul(offsetX, ftick); + left_slides[0].cy = fmul(offsetY, ftick); + } + + /* must change direction ? */ + if (target < index) + if (step > 0) + step = -1; + if (target > index) + if (step < 0) + step = 1; +} + + +/** + Cleanup the plugin +*/ +void cleanup(void *parameter) +{ + (void) parameter; + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(); /* backlight control in lib/helper.c */ + +#ifdef USEGSLIB + grey_release(); +#endif +} + +/** + Create the "?" slide, that is shown while loading + or when no cover was found. + */ +int create_empty_slide(bool force) +{ + if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) { + struct bitmap input_bmp; + int ret; + input_bmp.width = DISPLAY_WIDTH; + input_bmp.height = DISPLAY_HEIGHT; +#if LCD_DEPTH > 1 + input_bmp.format = FORMAT_NATIVE; +#endif + input_bmp.data = (char*)buf; + ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp, + buf_size, + FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT, + &format_transposed); + if (!save_pfraw(EMPTY_SLIDE, &input_bmp)) + return false; + } + + return true; +} + +/** + Shows the album name setting menu +*/ +int album_name_menu(void) +{ + int selection = show_album_name; + + MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL, + "Hide album title", "Show at the bottom", "Show at the top"); + rb->do_menu(&album_name_menu, &selection, NULL, false); + + show_album_name = selection; + return GO_TO_PREVIOUS; +} + +/** + Shows the settings menu + */ +int settings_menu(void) +{ + int selection = 0; + bool old_val; + + MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS", + "Spacing", "Centre margin", "Number of slides", "Zoom", + "Show album title", "Resize Covers", "Rebuild cache"); + + do { + selection=rb->do_menu(&settings_menu,&selection, NULL, false); + switch(selection) { + case 0: + rb->set_bool("Show FPS", &show_fps); + reset_track_list(); + break; + + case 1: + rb->set_int("Spacing between slides", "", 1, + &slide_spacing, + NULL, 1, 0, 100, NULL ); + recalc_offsets(); + reset_slides(); + break; + + case 2: + rb->set_int("Centre margin", "", 1, + ¢er_margin, + NULL, 1, 0, 80, NULL ); + recalc_offsets(); + reset_slides(); + break; + + case 3: + rb->set_int("Number of slides", "", 1, &num_slides, + NULL, 1, 1, MAX_SLIDES_COUNT, NULL ); + recalc_offsets(); + reset_slides(); + break; + + case 4: + rb->set_int("Zoom", "", 1, &zoom, + NULL, 1, 10, 300, NULL ); + recalc_offsets(); + reset_slides(); + break; + case 5: + album_name_menu(); + reset_track_list(); + recalc_offsets(); + reset_slides(); + break; + case 6: + old_val = resize; + rb->set_bool("Resize Covers", &resize); + if (old_val == resize) /* changed? */ + break; + /* fallthrough if changed, since cache needs to be rebuilt */ + case 7: + cache_version = 0; + rb->remove(EMPTY_SLIDE); + rb->splash(HZ, "Cache will be rebuilt on next restart"); + break; + + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + } + } while ( selection >= 0 ); + configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); + return 0; +} + +/** + Show the main menu + */ +int main_menu(void) +{ + int selection = 0; + int result; + +#if LCD_DEPTH > 1 + rb->lcd_set_foreground(N_BRIGHT(255)); +#endif + + MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL, + "Settings", "Return", "Quit"); + while (1) { + switch (rb->do_menu(&main_menu,&selection, NULL, false)) { + case 0: + result = settings_menu(); + if ( result != 0 ) return result; + break; + + case 1: + return 0; + + case 2: + return -1; + + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + + default: + return 0; + } + } +} + +/** + Animation step for zooming into the current cover + */ +void update_cover_in_animation(void) +{ + cover_animation_keyframe++; + if( cover_animation_keyframe < 20 ) { + center_slide.distance-=5; + center_slide.angle+=1; + extra_fade += 13; + } + else if( cover_animation_keyframe < 35 ) { + center_slide.angle+=16; + } + else { + cover_animation_keyframe = 0; + pf_state = pf_show_tracks; + } +} + +/** + Animation step for zooming out the current cover + */ +void update_cover_out_animation(void) +{ + cover_animation_keyframe++; + if( cover_animation_keyframe <= 15 ) { + center_slide.angle-=16; + } + else if( cover_animation_keyframe < 35 ) { + center_slide.distance+=5; + center_slide.angle-=1; + extra_fade -= 13; + } + else { + cover_animation_keyframe = 0; + pf_state = pf_idle; + } +} + +/** + Draw a blue gradient at y with height h + */ +static inline void draw_gradient(int y, int h) +{ + static int r, inc, c; + inc = (100 << 8) / h; + c = 0; + selected_track_pulse = (selected_track_pulse+1) % 10; + int c2 = selected_track_pulse - 5; + for (r=0; r> 9), c2+100-(c >> 9), + c2+250-(c >> 8))); +#else + MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8))); +#endif + MYLCD(hline)(0, LCD_WIDTH, r+y); + if ( r > h/2 ) + c-=inc; + else + c+=inc; + } +} + + +static void track_list_yh(int char_height) +{ + switch (show_album_name) + { + case album_name_hide: + track_list_y = (show_fps ? char_height : 0); + track_list_h = LCD_HEIGHT - track_list_y; + break; + case album_name_bottom: + track_list_y = (show_fps ? char_height : 0); + track_list_h = LCD_HEIGHT - track_list_y - char_height * 2; + break; + default: /* case album_name_top */ + track_list_y = char_height * 2; + track_list_h = LCD_HEIGHT - track_list_y - + (show_fps ? char_height : 0); + break; + } +} + +/** + Reset the track list after a album change + */ +void reset_track_list(void) +{ + int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight(); + track_list_yh(albumtxt_h); + track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count ); + start_index_track_list = 0; + track_scroll_index = 0; + track_scroll_dir = 1; + selected_track = 0; + + /* let the tracklist start more centered + * if the screen isn't filled with tracks */ + if (track_count*albumtxt_h < track_list_h) + { + track_list_h = track_count * albumtxt_h; + track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2); + } +} + +/** + Display the list of tracks + */ +void show_track_list(void) +{ + MYLCD(clear_display)(); + if ( center_slide.slide_index != track_index ) { + create_track_index(center_slide.slide_index); + reset_track_list(); + } + static int titletxt_w, titletxt_x, color, titletxt_h; + titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight(); + + int titletxt_y = track_list_y; + int track_i; + track_i = start_index_track_list; + for (;track_i < track_list_visible_entries+start_index_track_list; + track_i++) + { + MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL); + titletxt_x = (LCD_WIDTH-titletxt_w)/2; + if ( track_i == selected_track ) { + draw_gradient(titletxt_y, titletxt_h); + MYLCD(set_foreground)(G_BRIGHT(255)); + if (titletxt_w > LCD_WIDTH ) { + if ( titletxt_w + track_scroll_index <= LCD_WIDTH ) + track_scroll_dir = 1; + else if ( track_scroll_index >= 0 ) track_scroll_dir = -1; + track_scroll_index += track_scroll_dir*2; + titletxt_x = track_scroll_index; + } + MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i)); + } + else { + color = 250 - (abs(selected_track - track_i) * 200 / track_count); + MYLCD(set_foreground)(G_BRIGHT(color)); + MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i)); + } + titletxt_y += titletxt_h; + } +} + +void select_next_track(void) +{ + if ( selected_track < track_count - 1 ) { + selected_track++; + track_scroll_index = 0; + track_scroll_dir = 1; + if (selected_track==(track_list_visible_entries+start_index_track_list)) + start_index_track_list++; + } +} + +void select_prev_track(void) +{ + if (selected_track > 0 ) { + if (selected_track==start_index_track_list) start_index_track_list--; + track_scroll_index = 0; + track_scroll_dir = 1; + selected_track--; + } +} + +/** + Draw the current album name + */ +void draw_album_text(void) +{ + if (0 == show_album_name) + return; + + int albumtxt_w, albumtxt_h; + int albumtxt_y = 0; + + char *albumtxt; + int c; + /* Draw album text */ + if ( pf_state == pf_scrolling ) { + c = ((slide_frame & 0xffff )/ 255); + if (step < 0) c = 255-c; + if (c > 128 ) { /* half way to next slide .. still not perfect! */ + albumtxt = get_album_name(center_index+step); + c = (c-128)*2; + } + else { + albumtxt = get_album_name(center_index); + c = (128-c)*2; + } + } + else { + c= 255; + albumtxt = get_album_name(center_index); + } + + MYLCD(set_foreground)(G_BRIGHT(c)); + MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h); + if (center_index != prev_center_index) { + albumtxt_x = 0; + albumtxt_dir = -1; + prev_center_index = center_index; + } + + if (show_album_name == album_name_top) + albumtxt_y = albumtxt_h / 2; + else + albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2; + + if (albumtxt_w > LCD_WIDTH ) { + MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt); + if ( pf_state == pf_idle || pf_state == pf_show_tracks ) { + if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1; + else if ( albumtxt_x >= 0 ) albumtxt_dir = -1; + albumtxt_x += albumtxt_dir; + } + } + else { + MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt); + } + + +} + + +/** + Main function that also contain the main plasma + algorithm. + */ +int main(void) +{ + int ret; + + rb->lcd_setfont(FONT_UI); + draw_splashscreen(); + + if ( ! rb->dir_exists( CACHE_PREFIX ) ) { + if ( rb->mkdir( CACHE_PREFIX ) < 0 ) { + rb->splash(HZ, "Could not create directory " CACHE_PREFIX ); + return PLUGIN_ERROR; + } + } + + configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); + + init_reflect_table(); + + ALIGN_BUFFER(buf, buf_size, 4); + ret = create_album_index(); + if (ret == ERROR_BUFFER_FULL) { + rb->splash(HZ, "Not enough memory for album names"); + return PLUGIN_ERROR; + } else if (ret == ERROR_NO_ALBUMS) { + rb->splash(HZ, "No albums found. Please enable database"); + return PLUGIN_ERROR; + } + + ALIGN_BUFFER(buf, buf_size, 4); + number_of_slides = album_count; + if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) { + rb->splash(HZ, "Could not create album art cache"); + return PLUGIN_ERROR; + } + + if (!create_empty_slide(cache_version != CACHE_VERSION)) { + rb->splash(HZ, "Could not load the empty slide"); + return PLUGIN_ERROR; + } + cache_version = CACHE_VERSION; + configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION); + + +#ifdef USEGSLIB + long grey_buf_used; + if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP, + LCD_WIDTH, LCD_HEIGHT, &grey_buf_used)) + { + rb->splash(HZ, "Greylib init failed!"); + return PLUGIN_ERROR; + } + grey_setfont(FONT_UI); + buf_size -= grey_buf_used; + buf = (void*)(grey_buf_used + (char*)buf); +#endif + buflib_init(&buf_ctx, (void *)buf, buf_size); + + if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0))) + { + rb->splash(HZ, "Unable to load empty slide image"); + return PLUGIN_ERROR; + } + + if (!create_pf_thread()) { + rb->splash(HZ, "Cannot create thread!"); + return PLUGIN_ERROR; + } + + int i; + + /* initialize */ + for (i = 0; i < SLIDE_CACHE_SIZE; i++) { + cache[i].hid = 0; + cache[i].index = 0; + cache[i].next = i + 1; + cache[i].prev = i - 1; + } + cache[0].prev = i - 1; + cache[i - 1].next = 0; + cache_free = 0; + buffer = LCD_BUF; + + pf_state = pf_idle; + + track_index = -1; + extra_fade = 0; + slide_frame = 0; + step = 0; + target = 0; + fade = 256; + + recalc_offsets(); + reset_slides(); + + char fpstxt[10]; + int button; + + int frames = 0; + long last_update = *rb->current_tick; + long current_update; + long update_interval = 100; + int fps = 0; + int fpstxt_y; + + bool instant_update; +#ifdef USEGSLIB + grey_show(true); + grey_set_drawmode(DRMODE_FG); +#endif + rb->lcd_set_drawmode(DRMODE_FG); + while (true) { + current_update = *rb->current_tick; + frames++; + + /* Initial rendering */ + instant_update = false; + + /* Handle states */ + switch ( pf_state ) { + case pf_scrolling: + update_scroll_animation(); + render_all_slides(); + instant_update = true; + break; + case pf_cover_in: + update_cover_in_animation(); + render_all_slides(); + instant_update = true; + break; + case pf_cover_out: + update_cover_out_animation(); + render_all_slides(); + instant_update = true; + break; + case pf_show_tracks: + show_track_list(); + break; + case pf_idle: + render_all_slides(); + break; + } + + /* Calculate FPS */ + if (current_update - last_update > update_interval) { + fps = frames * HZ / (current_update - last_update); + last_update = current_update; + frames = 0; + } + /* Draw FPS */ + if (show_fps) + { +#ifdef USEGSLIB + MYLCD(set_foreground)(G_BRIGHT(255)); +#else + MYLCD(set_foreground)(G_PIX(255,0,0)); +#endif + rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps); + if (show_album_name == album_name_top) + fpstxt_y = LCD_HEIGHT - + rb->screens[SCREEN_MAIN]->getcharheight(); + else + fpstxt_y = 0; + MYLCD(putsxy)(0, fpstxt_y, fpstxt); + } + draw_album_text(); + + + /* Copy offscreen buffer to LCD and give time to other threads */ + MYLCD(update)(); + rb->yield(); + + /*/ Handle buttons */ + button = rb->get_custom_action(CONTEXT_CUSTOM| + (pf_state == pf_show_tracks ? 1 : 0), + instant_update ? 0 : HZ/16, + get_context_map); + + switch (button) { + case PF_QUIT: + return PLUGIN_OK; + + case PF_BACK: + if ( pf_state == pf_show_tracks ) + pf_state = pf_cover_out; + if (pf_state == pf_idle || pf_state == pf_scrolling) + return PLUGIN_OK; + break; + + case PF_MENU: +#ifdef USEGSLIB + grey_show(false); +#endif + ret = main_menu(); + if ( ret == -1 ) return PLUGIN_OK; + if ( ret != 0 ) return i; +#ifdef USEGSLIB + grey_show(true); +#endif + MYLCD(set_drawmode)(DRMODE_FG); + break; + + case PF_NEXT: + case PF_NEXT_REPEAT: + if ( pf_state == pf_show_tracks ) + select_next_track(); + if ( pf_state == pf_idle || pf_state == pf_scrolling ) + show_next_slide(); + break; + + case PF_PREV: + case PF_PREV_REPEAT: + if ( pf_state == pf_show_tracks ) + select_prev_track(); + if ( pf_state == pf_idle || pf_state == pf_scrolling ) + show_previous_slide(); + break; + + case PF_SELECT: + if ( pf_state == pf_idle ) { + pf_state = pf_cover_in; + } + break; + + default: + if (rb->default_event_handler_ex(button, cleanup, NULL) + == SYS_USB_CONNECTED) + return PLUGIN_USB_CONNECTED; + break; + } + } + + +} + +/*************************** Plugin entry point ****************************/ + +enum plugin_status plugin_start(const void *parameter) +{ + int ret; + (void) parameter; +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop(NULL); +#endif + /* Turn off backlight timeout */ + backlight_force_on(); /* backlight control in lib/helper.c */ +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif +#if PLUGIN_BUFFER_SIZE > 0x10000 && 0 + buf = rb->plugin_get_buffer(&buf_size); +#else + buf = rb->plugin_get_audio_buffer(&buf_size); +#endif + ret = main(); +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + if ( ret == PLUGIN_OK ) { + if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, + CONFIG_VERSION)) + { + rb->splash(HZ, "Error writing config."); + ret = PLUGIN_ERROR; + } + } + + end_pf_thread(); + cleanup(NULL); + return ret; +} diff --git a/apps/plugins/pictureflow/pictureflow.make b/apps/plugins/pictureflow/pictureflow.make new file mode 100644 index 0000000000..c1fb1f3957 --- /dev/null +++ b/apps/plugins/pictureflow/pictureflow.make @@ -0,0 +1,58 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +PICTUREFLOW_SRCDIR = $(APPSDIR)/plugins/pictureflow +PICTUREFLOW_OBJDIR = $(BUILDDIR)/apps/plugins/pictureflow + +PICTUREFLOW_SRC := $(call preprocess, $(PICTUREFLOW_SRCDIR)/SOURCES) +PICTUREFLOW_OBJ := $(call c2obj, $(PICTUREFLOW_SRC)) + +OTHER_SRC += $(PICTUREFLOW_SRC) + +ifndef SIMVER +ifneq (,$(strip $(foreach tgt,RECORDER ONDIO,$(findstring $(tgt),$(TARGET))))) + ### lowmem targets + ROCKS += $(PICTUREFLOW_OBJDIR)/pictureflow.ovl + PICTUREFLOW_OUTLDS = $(PICTUREFLOW_OBJDIR)/picutreflow.link + PICTUREFLOW_OVLFLAGS = -T$(PICTUREFLOW_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map +else + ### all other targets + ROCKS += $(PICTUREFLOW_OBJDIR)/pictureflow.rock +endif +else + ### simulator + ROCKS += $(PICTUREFLOW_OBJDIR)/pictureflow.rock +endif + +ifeq ($(CPU),sh) +# sh need to retain its' -Os +PICTUREFLOWFLAGS = $(PLUGINFLAGS) +else +PICTUREFLOWFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O2 +endif + +$(PICTUREFLOW_OBJDIR)/pictureflow.rock: $(PICTUREFLOW_OBJ) + +$(PICTUREFLOW_OBJDIR)/pictureflow.refmap: $(PICTUREFLOW_OBJ) + +$(PICTUREFLOW_OUTLDS): $(PLUGIN_LDS) $(PICTUREFLOW_OBJDIR)/pictureflow.refmap + $(call PRINTS,PP $(@F))$(call preprocess2file,$<,$@,-DOVERLAY_OFFSET=$(shell \ + $(TOOLSDIR)/ovl_offset.pl $(PICTUREFLOW_OBJDIR)/pictureflow.refmap)) + +$(PICTUREFLOW_OBJDIR)/pictureflow.ovl: $(PICTUREFLOW_OBJ) $(PICTUREFLOW_OUTLDS) + $(SILENT)$(CC) $(PLUGINFLAGS) -o $(basename $@).elf \ + $(filter %.o, $^) \ + $(filter %.a, $+) \ + -lgcc $(PICTUREFLOW_OVLFLAGS) + $(call PRINTS,LD $(@F))$(OC) -O binary $(basename $@).elf $@ + +# special pattern rule for compiling pictureflow with extra flags +$(PICTUREFLOW_OBJDIR)/%.o: $(PICTUREFLOW_SRCDIR)/%.c $(PLUGINBITMAPLIB) $(PICTUREFLOW_SRCDIR)/pictureflow.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(PICTUREFLOWFLAGS) -c $< -o $@ -- cgit