talk_init() : don't try to load the voice file if it won't fit in memory
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob463f86c39452d55e7e04cbb70fe62f3c9fdadcd0
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
26 #include "plugin.h"
27 #include <albumart.h>
28 #include "lib/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/helper.h"
31 #include "lib/configfile.h"
32 #include "lib/grey.h"
33 #include "lib/feature_wrappers.h"
34 #include "lib/buflib.h"
36 PLUGIN_HEADER
38 /******************************* Globals ***********************************/
41 * Targets which use plugin_get_audio_buffer() can't have playback from
42 * within pictureflow itself, as the whole core audio buffer is occupied */
43 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
45 #if PF_PLAYBACK_CAPABLE
46 #include "lib/playback_control.h"
47 #endif
49 #define PF_PREV ACTION_STD_PREV
50 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
51 #define PF_NEXT ACTION_STD_NEXT
52 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
53 #define PF_SELECT ACTION_STD_OK
54 #define PF_CONTEXT ACTION_STD_CONTEXT
55 #define PF_BACK ACTION_STD_CANCEL
56 #define PF_MENU ACTION_STD_MENU
57 #define PF_WPS ACTION_TREE_WPS
59 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
61 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
62 CONFIG_KEYPAD == SAMSUNG_YH_PAD
63 #define USE_CORE_PREVNEXT
64 #endif
66 #ifndef USE_CORE_PREVNEXT
67 /* scrollwheel targets use the wheel, just as they do in lists,
68 * so there's no need for a special context,
69 * others use left/right here too (as oppsed to up/down in lists) */
70 const struct button_mapping pf_context_album_scroll[] =
72 #ifdef HAVE_TOUCHSCREEN
73 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
74 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
75 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
76 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
77 #endif
78 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
79 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
80 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
81 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
82 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
83 #else
84 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
85 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
86 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
87 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
88 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
89 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
90 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
91 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
92 #endif
93 #if CONFIG_KEYPAD == ONDIO_PAD
94 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
95 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
96 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
97 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
98 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
99 #endif
100 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
102 #endif /* !USE_CORE_PREVNEXT */
104 const struct button_mapping pf_context_buttons[] =
106 #ifdef HAVE_TOUCHSCREEN
107 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
108 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
109 #endif
110 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
111 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
112 #elif CONFIG_KEYPAD == SANSA_C100_PAD
113 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
114 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
115 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
116 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
117 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
118 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
119 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
120 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
121 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
122 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
124 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
125 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
126 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
127 #if CONFIG_KEYPAD == COWON_D2_PAD
128 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
129 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
130 #endif
131 #elif CONFIG_KEYPAD == SANSA_E200_PAD
132 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
133 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
134 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
135 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
136 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
137 || (CONFIG_KEYPAD == IPOD_4G_PAD)
138 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
139 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
140 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
141 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
142 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
143 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
144 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
145 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
146 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
147 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
148 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
149 {PF_QUIT, BUTTON_REC, BUTTON_NONE},
150 #endif
151 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
152 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
153 #else
154 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
155 #endif
157 const struct button_mapping *pf_contexts[] =
159 #ifndef USE_CORE_PREVNEXT
160 pf_context_album_scroll,
161 #endif
162 pf_context_buttons
165 #if LCD_DEPTH < 8
166 #if LCD_DEPTH > 1
167 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
168 #else /* LCD_DEPTH <= 1 */
169 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
170 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
171 #define PICTUREFLOW_DRMODE DRMODE_SOLID
172 #else
173 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
174 #endif
175 #endif /* LCD_DEPTH <= 1 */
176 #define USEGSLIB
177 GREY_INFO_STRUCT
178 #define LCD_BUF _grey_info.buffer
179 #define MYLCD(fn) grey_ ## fn
180 #define G_PIX(r,g,b) \
181 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
182 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
183 #define G_BRIGHT(y) (y)
184 #define BUFFER_WIDTH _grey_info.width
185 #define BUFFER_HEIGHT _grey_info.height
186 typedef unsigned char pix_t;
187 #else /* LCD_DEPTH >= 8 */
188 #define LCD_BUF rb->lcd_framebuffer
189 #define MYLCD(fn) rb->lcd_ ## fn
190 #define G_PIX LCD_RGBPACK
191 #define N_PIX LCD_RGBPACK
192 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
193 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
194 #define BUFFER_WIDTH LCD_WIDTH
195 #define BUFFER_HEIGHT LCD_HEIGHT
196 typedef fb_data pix_t;
197 #endif /* LCD_DEPTH >= 8 */
199 /* for fixed-point arithmetic, we need minimum 32-bit long
200 long long (64-bit) might be useful for multiplication and division */
201 #define PFreal long
202 #define PFREAL_SHIFT 10
203 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
204 #define PFREAL_ONE (1 << PFREAL_SHIFT)
205 #define PFREAL_HALF (PFREAL_ONE >> 1)
208 #define IANGLE_MAX 1024
209 #define IANGLE_MASK 1023
211 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
212 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
213 #define DISPLAY_HEIGHT REFLECT_TOP
214 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
215 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
216 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
217 (REFLECT_HEIGHT * 5))
218 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
219 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
220 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
221 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
222 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
224 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
226 #define MAX_SLIDES_COUNT 10
228 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
229 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
231 #define EV_EXIT 9999
232 #define EV_WAKEUP 1337
234 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
235 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
236 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
238 /* Error return values */
239 #define ERROR_NO_ALBUMS -1
240 #define ERROR_BUFFER_FULL -2
242 /* current version for cover cache */
243 #define CACHE_VERSION 3
244 #define CONFIG_VERSION 1
245 #define CONFIG_FILE "pictureflow.cfg"
247 /** structs we use */
249 struct slide_data {
250 int slide_index;
251 int angle;
252 PFreal cx;
253 PFreal cy;
254 PFreal distance;
257 struct slide_cache {
258 int index; /* index of the cached slide */
259 int hid; /* handle ID of the cached slide */
260 short next; /* "next" slide, with LRU last */
261 short prev; /* "previous" slide */
264 struct album_data {
265 int name_idx;
266 long seek;
269 struct track_data {
270 uint32_t sort;
271 int name_idx; /* offset to the track name */
272 long seek;
273 #if PF_PLAYBACK_CAPABLE
274 /* offset to the filename in the string, needed for playlist generation */
275 int filename_idx;
276 #endif
279 struct rect {
280 int left;
281 int right;
282 int top;
283 int bottom;
286 struct load_slide_event_data {
287 int slide_index;
288 int cache_index;
292 struct pfraw_header {
293 int32_t width; /* bmap width in pixels */
294 int32_t height; /* bmap height in pixels */
297 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
298 album_name_top };
299 static char* show_album_name_conf[] =
301 "hide",
302 "bottom",
303 "top"
306 #define MAX_SPACING 40
307 #define MAX_MARGIN 80
309 /* config values and their defaults */
310 static int slide_spacing = DISPLAY_WIDTH / 4;
311 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
312 static int num_slides = 4;
313 static int zoom = 100;
314 static bool show_fps = false;
315 static bool resize = true;
316 static int cache_version = 0;
317 static int show_album_name = (LCD_HEIGHT > 100)
318 ? album_name_top : album_name_bottom;
320 static struct configdata config[] =
322 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
323 NULL },
324 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
325 NULL },
326 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
327 NULL },
328 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
329 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
330 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
331 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
332 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
333 show_album_name_conf }
336 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
338 /** below we allocate the memory we want to use **/
340 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
341 static uint8_t reflect_table[REFLECT_HEIGHT];
342 static struct slide_data center_slide;
343 static struct slide_data left_slides[MAX_SLIDES_COUNT];
344 static struct slide_data right_slides[MAX_SLIDES_COUNT];
345 static int slide_frame;
346 static int step;
347 static int target;
348 static int fade;
349 static int center_index = 0; /* index of the slide that is in the center */
350 static int itilt;
351 static PFreal offsetX;
352 static PFreal offsetY;
353 static int number_of_slides;
355 static struct slide_cache cache[SLIDE_CACHE_SIZE];
356 static int cache_free;
357 static int cache_used = -1;
358 static int cache_left_index = -1;
359 static int cache_right_index = -1;
360 static int cache_center_index = -1;
362 /* use long for aligning */
363 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
364 /* queue (as array) for scheduling load_surface */
366 static int empty_slide_hid;
368 unsigned int thread_id;
369 struct event_queue thread_q;
371 static struct tagcache_search tcs;
373 static struct buflib_context buf_ctx;
375 static struct album_data *album;
376 static char *album_names;
377 static int album_count;
379 static struct track_data *tracks;
380 static char *track_names;
381 static size_t borrowed = 0;
382 static int track_count;
383 static int track_index;
384 static int selected_track;
385 static int selected_track_pulse;
386 void reset_track_list(void);
388 void * buf;
389 size_t buf_size;
391 static bool thread_is_running;
393 static int cover_animation_keyframe;
394 static int extra_fade;
396 static int albumtxt_x = 0;
397 static int albumtxt_dir = -1;
398 static int prev_center_index = -1;
400 static int start_index_track_list = 0;
401 static int track_list_visible_entries = 0;
402 static int track_list_y;
403 static int track_list_h;
404 static int track_scroll_index = 0;
405 static int track_scroll_dir = 1;
408 Proposals for transitions:
410 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
411 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
413 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
415 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
417 TODO:
418 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
419 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
421 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
423 enum pf_states {
424 pf_idle = 0,
425 pf_scrolling,
426 pf_cover_in,
427 pf_show_tracks,
428 pf_cover_out
431 static int pf_state;
433 /** code */
434 static bool free_slide_prio(int prio);
435 static inline unsigned fade_color(pix_t c, unsigned a);
436 bool save_pfraw(char* filename, struct bitmap *bm);
437 bool load_new_slide(void);
438 int load_surface(int);
440 static inline PFreal fmul(PFreal a, PFreal b)
442 return (a*b) >> PFREAL_SHIFT;
446 * This version preshifts each operand, which is useful when we know how many
447 * of the least significant bits will be empty, or are worried about overflow
448 * in a particular calculation
450 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
452 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
455 /* ARMv5+ has a clz instruction equivalent to our function.
457 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
458 static inline int clz(uint32_t v)
460 return __builtin_clz(v);
463 /* Otherwise, use our clz, which can be inlined */
464 #elif defined(CPU_COLDFIRE)
465 /* This clz is based on the log2(n) implementation at
466 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
467 * A clz benchmark plugin showed this to be about 14% faster on coldfire
468 * than the LUT-based version.
470 static inline int clz(uint32_t v)
472 int r = 32;
473 if (v >= 0x10000)
475 v >>= 16;
476 r -= 16;
478 if (v & 0xff00)
480 v >>= 8;
481 r -= 8;
483 if (v & 0xf0)
485 v >>= 4;
486 r -= 4;
488 if (v & 0xc)
490 v >>= 2;
491 r -= 2;
493 if (v & 2)
495 v >>= 1;
496 r -= 1;
498 r -= v;
499 return r;
501 #else
502 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
503 0, 0, 0, 0, 0, 0, 0, 0 };
504 /* This clz is based on the log2(n) implementation at
505 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
506 * It is not any faster than the one above, but trades 16B in the lookup table
507 * for a savings of 12B per each inlined call.
509 static inline int clz(uint32_t v)
511 int r = 28;
512 if (v >= 0x10000)
514 v >>= 16;
515 r -= 16;
517 if (v & 0xff00)
519 v >>= 8;
520 r -= 8;
522 if (v & 0xf0)
524 v >>= 4;
525 r -= 4;
527 return r + clz_lut[v];
529 #endif
531 /* Return the maximum possible left shift for a signed int32, without
532 * overflow
534 static inline int allowed_shift(int32_t val)
536 uint32_t uval = val ^ (val >> 31);
537 return clz(uval) - 1;
540 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
541 * num and den before dividing.
543 static inline PFreal fdiv(PFreal num, PFreal den)
545 int shift = allowed_shift(num);
546 shift = MIN(PFREAL_SHIFT, shift);
547 num <<= shift;
548 den >>= PFREAL_SHIFT - shift;
549 return num / den;
552 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
553 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
554 #define fabs(a) (a < 0 ? -a : a)
555 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
557 #if CONFIG_CPU == SH7034
558 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
559 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
560 #else
561 #define MULUQ(a, b) ((a) * (b))
562 #endif
565 #if 0
566 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
567 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
569 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
570 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
572 static inline PFreal fmul(PFreal a, PFreal b)
574 return (a*b) >> PFREAL_SHIFT;
577 static inline PFreal fdiv(PFreal n, PFreal m)
579 return (n<<(PFREAL_SHIFT))/m;
581 #endif
583 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
584 static const short sin_tab[] = {
585 0, 100, 200, 297, 392, 483, 569, 650,
586 724, 792, 851, 903, 946, 980, 1004, 1019,
587 1024, 1019, 1004, 980, 946, 903, 851, 792,
588 724, 650, 569, 483, 392, 297, 200, 100,
589 0, -100, -200, -297, -392, -483, -569, -650,
590 -724, -792, -851, -903, -946, -980, -1004, -1019,
591 -1024, -1019, -1004, -980, -946, -903, -851, -792,
592 -724, -650, -569, -483, -392, -297, -200, -100,
596 static inline PFreal fsin(int iangle)
598 iangle &= IANGLE_MASK;
600 int i = (iangle >> 4);
601 PFreal p = sin_tab[i];
602 PFreal q = sin_tab[(i+1)];
603 PFreal g = (q - p);
604 return p + g * (iangle-i*16)/16;
607 static inline PFreal fcos(int iangle)
609 return fsin(iangle + (IANGLE_MAX >> 2));
612 static inline unsigned scale_val(unsigned val, unsigned bits)
614 val = val * ((1 << bits) - 1);
615 return ((val >> 8) + val + 128) >> 8;
618 static void output_row_8_transposed(uint32_t row, void * row_in,
619 struct scaler_context *ctx)
621 pix_t *dest = (pix_t*)ctx->bm->data + row;
622 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
623 #ifdef USEGSLIB
624 uint8_t *qp = (uint8_t*)row_in;
625 for (; dest < end; dest += ctx->bm->height)
626 *dest = *qp++;
627 #else
628 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
629 unsigned r, g, b;
630 for (; dest < end; dest += ctx->bm->height)
632 r = scale_val(qp->red, 5);
633 g = scale_val(qp->green, 6);
634 b = scale_val((qp++)->blue, 5);
635 *dest = LCD_RGBPACK_LCD(r,g,b);
637 #endif
640 static void output_row_32_transposed(uint32_t row, void * row_in,
641 struct scaler_context *ctx)
643 pix_t *dest = (pix_t*)ctx->bm->data + row;
644 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
645 #ifdef USEGSLIB
646 uint32_t *qp = (uint32_t*)row_in;
647 for (; dest < end; dest += ctx->bm->height)
648 *dest = SC_OUT(*qp++, ctx);
649 #else
650 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
651 int r, g, b;
652 for (; dest < end; dest += ctx->bm->height)
654 r = scale_val(SC_OUT(qp->r, ctx), 5);
655 g = scale_val(SC_OUT(qp->g, ctx), 6);
656 b = scale_val(SC_OUT(qp->b, ctx), 5);
657 qp++;
658 *dest = LCD_RGBPACK_LCD(r,g,b);
660 #endif
663 #ifdef HAVE_LCD_COLOR
664 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
665 struct scaler_context *ctx)
667 pix_t *dest = (pix_t*)ctx->bm->data + row;
668 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
669 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
670 for (; dest < end; dest += ctx->bm->height)
672 unsigned r, g, b, y, u, v;
673 y = SC_OUT(qp->b, ctx);
674 u = SC_OUT(qp->g, ctx);
675 v = SC_OUT(qp->r, ctx);
676 qp++;
677 yuv_to_rgb(y, u, v, &r, &g, &b);
678 r = scale_val(r, 5);
679 g = scale_val(g, 6);
680 b = scale_val(b, 5);
681 *dest = LCD_RGBPACK_LCD(r, g, b);
684 #endif
686 static unsigned int get_size(struct bitmap *bm)
688 return bm->width * bm->height * sizeof(pix_t);
691 const struct custom_format format_transposed = {
692 .output_row_8 = output_row_8_transposed,
693 #ifdef HAVE_LCD_COLOR
694 .output_row_32 = {
695 output_row_32_transposed,
696 output_row_32_transposed_fromyuv
698 #else
699 .output_row_32 = output_row_32_transposed,
700 #endif
701 .get_size = get_size
704 static const struct button_mapping* get_context_map(int context)
706 return pf_contexts[context & ~CONTEXT_PLUGIN];
709 /* Create the lookup table with the scaling values for the reflections */
710 void init_reflect_table(void)
712 int i;
713 for (i = 0; i < REFLECT_HEIGHT; i++)
714 reflect_table[i] =
715 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
716 (5 * REFLECT_HEIGHT);
720 Create an index of all albums from the database.
721 Also store the album names so we can access them later.
723 int create_album_index(void)
725 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
726 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
727 album_count = 0;
728 rb->tagcache_search(&tcs, tag_album);
729 unsigned int l, old_l = 0;
730 album_names = buf;
731 album[0].name_idx = 0;
732 while (rb->tagcache_get_next(&tcs))
734 buf_size -= sizeof(struct album_data);
735 l = tcs.result_len;
736 if ( album_count > 0 )
737 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
739 if ( l > buf_size )
740 /* not enough memory */
741 return ERROR_BUFFER_FULL;
743 rb->strcpy(buf, tcs.result);
744 buf_size -= l;
745 buf = l + (char *)buf;
746 album[-album_count].seek = tcs.result_seek;
747 old_l = l;
748 album_count++;
750 rb->tagcache_search_finish(&tcs);
751 ALIGN_BUFFER(buf, buf_size, 4);
752 int i;
753 struct album_data* tmp_album = (struct album_data*)buf;
754 for (i = album_count - 1; i >= 0; i--)
755 tmp_album[i] = album[-i];
756 album = tmp_album;
757 buf = album + album_count;
758 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
762 Return a pointer to the album name of the given slide_index
764 char* get_album_name(const int slide_index)
766 return album_names + album[slide_index].name_idx;
770 Return a pointer to the track name of the active album
771 create_track_index has to be called first.
773 char* get_track_name(const int track_index)
775 if ( track_index < track_count )
776 return track_names + tracks[track_index].name_idx;
777 return 0;
779 #if PF_PLAYBACK_CAPABLE
780 char* get_track_filename(const int track_index)
782 if ( track_index < track_count )
783 return track_names + tracks[track_index].filename_idx;
784 return 0;
786 #endif
788 Compare two unsigned ints passed via pointers.
790 int compare_tracks (const void *a_v, const void *b_v)
792 uint32_t a = ((struct track_data *)a_v)->sort;
793 uint32_t b = ((struct track_data *)b_v)->sort;
794 return (int)(a - b);
798 Create the track index of the given slide_index.
800 void create_track_index(const int slide_index)
802 if ( slide_index == track_index )
803 return;
804 track_index = slide_index;
806 if (!rb->tagcache_search(&tcs, tag_title))
807 goto fail;
809 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
810 track_count=0;
811 int string_index = 0, track_num;
812 int disc_num;
813 size_t out = 0;
814 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
815 borrowed += out;
816 int avail = borrowed;
817 tracks = (struct track_data*)(track_names + borrowed);
818 while (rb->tagcache_get_next(&tcs))
820 int len = 0, fn_idx = 0;
822 avail -= sizeof(struct track_data);
823 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
824 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
826 if (disc_num < 0)
827 disc_num = 0;
828 retry:
829 if (track_num >= 0)
831 if (disc_num)
832 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
833 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
834 else
835 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
836 "%d: %s", track_num + 1, tcs.result);
838 else
840 track_num = 0;
841 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
842 "%s", tcs.result);
844 if (fn_idx <= 0)
845 goto fail;
846 #if PF_PLAYBACK_CAPABLE
847 int remain = avail - fn_idx;
848 if (remain >= MAX_PATH)
849 { /* retrieve filename for building the playlist */
850 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
851 track_names + string_index + fn_idx, remain);
852 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
853 /* make sure track name and file name are really split by a \0, else
854 * get_track_name might fail */
855 *(track_names + string_index + fn_idx -1) = '\0';
858 else /* request more buffer so that track and filename fit */
859 len = (avail - remain) + MAX_PATH;
860 #else
861 len = fn_idx;
862 #endif
863 if (len > avail)
865 while (len > avail)
867 if (!free_slide_prio(0))
868 goto fail;
869 out = 0;
870 buflib_buffer_out(&buf_ctx, &out);
871 avail += out;
872 borrowed += out;
873 if (track_count)
875 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
876 unsigned int bytes = track_count * sizeof(struct track_data);
877 rb->memmove(new_tracks, tracks, bytes);
878 tracks = new_tracks;
881 goto retry;
884 avail -= len;
885 tracks--;
886 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
887 tracks->name_idx = string_index;
888 tracks->seek = tcs.result_seek;
889 #if PF_PLAYBACK_CAPABLE
890 tracks->filename_idx = fn_idx + string_index;
891 #endif
892 track_count++;
893 string_index += len;
896 rb->tagcache_search_finish(&tcs);
898 /* now fix the track list order */
899 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
900 return;
901 fail:
902 track_count = 0;
903 return;
907 Determine filename of the album art for the given slide_index and
908 store the result in buf.
909 The algorithm looks for the first track of the given album uses
910 find_albumart to find the filename.
912 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
913 int buflen)
915 if ( slide_index == -1 )
917 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
920 if (!rb->tagcache_search(&tcs, tag_filename))
921 return false;
923 bool result;
924 /* find the first track of the album */
925 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
927 if ( rb->tagcache_get_next(&tcs) ) {
928 struct mp3entry id3;
929 int fd;
931 #ifdef HAVE_TC_RAMCACHE
932 if (rb->tagcache_fill_tags(&id3, tcs.result))
934 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
936 else
937 #endif
939 fd = rb->open(tcs.result, O_RDONLY);
940 rb->get_metadata(&id3, fd, tcs.result);
941 rb->close(fd);
943 if ( search_albumart_files(&id3, ":", buf, buflen) )
944 result = true;
945 else
946 result = false;
948 else {
949 /* did not find a matching track */
950 result = false;
952 rb->tagcache_search_finish(&tcs);
953 return result;
957 Draw the PictureFlow logo
959 void draw_splashscreen(void)
961 unsigned char * buf_tmp = buf;
962 size_t buf_tmp_size = buf_size;
963 struct screen* display = rb->screens[0];
964 #if FB_DATA_SZ > 1
965 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
966 #endif
967 struct bitmap logo = {
968 #if LCD_WIDTH < 200
969 .width = 100,
970 .height = 18,
971 #else
972 .width = 193,
973 .height = 34,
974 #endif
975 .data = buf_tmp
977 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
978 NULL);
979 #if LCD_DEPTH > 1
980 rb->lcd_set_background(N_BRIGHT(0));
981 rb->lcd_set_foreground(N_BRIGHT(255));
982 #else
983 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
984 #endif
985 rb->lcd_clear_display();
987 if (ret > 0)
989 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
990 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
991 #endif
992 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
993 logo.width, logo.height);
994 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
995 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
996 #endif
999 rb->lcd_update();
1004 Draw a simple progress bar
1006 void draw_progressbar(int step)
1008 int txt_w, txt_h;
1009 const int bar_height = 22;
1010 const int w = LCD_WIDTH - 20;
1011 const int x = 10;
1013 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1015 int y = (LCD_HEIGHT - txt_h)/2;
1017 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1018 y += (txt_h + 5);
1020 #if LCD_DEPTH > 1
1021 rb->lcd_set_foreground(N_BRIGHT(100));
1022 #endif
1023 rb->lcd_drawrect(x, y, w+2, bar_height);
1024 #if LCD_DEPTH > 1
1025 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1026 #endif
1028 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1029 #if LCD_DEPTH > 1
1030 rb->lcd_set_foreground(N_BRIGHT(255));
1031 #endif
1032 rb->lcd_update();
1033 rb->yield();
1037 Precomupte the album art images and store them in CACHE_PREFIX.
1039 bool create_albumart_cache(void)
1041 int ret;
1043 int i, slides = 0;
1044 struct bitmap input_bmp;
1046 char pfraw_file[MAX_PATH];
1047 char albumart_file[MAX_PATH];
1048 unsigned int format = FORMAT_NATIVE;
1049 cache_version = 0;
1050 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1051 if (resize)
1052 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1053 for (i=0; i < album_count; i++)
1055 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1057 /* delete existing cache, so it's a true rebuild */
1058 if(rb->file_exists(pfraw_file))
1059 rb->remove(pfraw_file);
1060 draw_progressbar(i);
1061 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1062 continue;
1064 input_bmp.data = buf;
1065 input_bmp.width = DISPLAY_WIDTH;
1066 input_bmp.height = DISPLAY_HEIGHT;
1067 ret = read_image_file(albumart_file, &input_bmp,
1068 buf_size, format, &format_transposed);
1069 if (ret <= 0) {
1070 rb->splash(HZ, "Could not read bmp");
1071 continue; /* skip missing/broken files */
1073 if (!save_pfraw(pfraw_file, &input_bmp))
1075 rb->splash(HZ, "Could not write bmp");
1077 slides++;
1078 if ( rb->button_get(false) == PF_MENU ) return false;
1080 if ( slides == 0 ) {
1081 /* Warn the user that we couldn't find any albumart */
1082 rb->splash(2*HZ, "No album art found");
1083 return false;
1085 return true;
1089 Thread used for loading and preparing bitmaps in the background
1091 void thread(void)
1093 long sleep_time = 5 * HZ;
1094 struct queue_event ev;
1095 while (1) {
1096 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1097 switch (ev.id) {
1098 case EV_EXIT:
1099 return;
1100 case EV_WAKEUP:
1101 /* we just woke up */
1102 break;
1104 while ( load_new_slide() ) {
1105 rb->yield();
1106 switch (ev.id) {
1107 case EV_EXIT:
1108 return;
1116 End the thread by posting the EV_EXIT event
1118 void end_pf_thread(void)
1120 if ( thread_is_running ) {
1121 rb->queue_post(&thread_q, EV_EXIT, 0);
1122 rb->thread_wait(thread_id);
1123 /* remove the thread's queue from the broadcast list */
1124 rb->queue_delete(&thread_q);
1125 thread_is_running = false;
1132 Create the thread an setup the event queue
1134 bool create_pf_thread(void)
1136 /* put the thread's queue in the bcast list */
1137 rb->queue_init(&thread_q, true);
1138 if ((thread_id = rb->create_thread(
1139 thread,
1140 thread_stack,
1141 sizeof(thread_stack),
1143 "Picture load thread"
1144 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1145 PRIORITY_REALTIME + 1))
1146 IF_COP(, CPU)
1148 ) == 0) {
1149 return false;
1151 thread_is_running = true;
1152 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1153 return true;
1157 Safe the given bitmap as filename in the pfraw format
1159 bool save_pfraw(char* filename, struct bitmap *bm)
1161 struct pfraw_header bmph;
1162 bmph.width = bm->width;
1163 bmph.height = bm->height;
1164 int fh = rb->creat( filename , 0666);
1165 if( fh < 0 ) return false;
1166 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1167 int y;
1168 for( y = 0; y < bm->height; y++ )
1170 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1171 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1173 rb->close( fh );
1174 return true;
1179 * The following functions implement the linked-list-in-array used to manage
1180 * the LRU cache of slides, and the list of free cache slots.
1183 #define seek_right_while(start, cond) \
1184 ({ \
1185 int ind_, next_ = (start); \
1186 do { \
1187 ind_ = next_; \
1188 next_ = cache[ind_].next; \
1189 } while (next_ != cache_used && (cond)); \
1190 ind_; \
1193 #define seek_left_while(start, cond) \
1194 ({ \
1195 int ind_, next_ = (start); \
1196 do { \
1197 ind_ = next_; \
1198 next_ = cache[ind_].prev; \
1199 } while (ind_ != cache_used && (cond)); \
1200 ind_; \
1204 Pop the given item from the linked list starting at *head, returning the next
1205 item, or -1 if the list is now empty.
1207 static inline int lla_pop_item (int *head, int i)
1209 int prev = cache[i].prev;
1210 int next = cache[i].next;
1211 if (i == next)
1213 *head = -1;
1214 return -1;
1216 else if (i == *head)
1217 *head = next;
1218 cache[next].prev = prev;
1219 cache[prev].next = next;
1220 return next;
1225 Pop the head item from the list starting at *head, returning the index of the
1226 item, or -1 if the list is already empty.
1228 static inline int lla_pop_head (int *head)
1230 int i = *head;
1231 if (i != -1)
1232 lla_pop_item(head, i);
1233 return i;
1237 Insert the item at index i before the one at index p.
1239 static inline void lla_insert (int i, int p)
1241 int next = p;
1242 int prev = cache[next].prev;
1243 cache[next].prev = i;
1244 cache[prev].next = i;
1245 cache[i].next = next;
1246 cache[i].prev = prev;
1251 Insert the item at index i at the end of the list starting at *head.
1253 static inline void lla_insert_tail (int *head, int i)
1255 if (*head == -1)
1257 *head = i;
1258 cache[i].next = i;
1259 cache[i].prev = i;
1260 } else
1261 lla_insert(i, *head);
1265 Insert the item at index i before the one at index p.
1267 static inline void lla_insert_after(int i, int p)
1269 p = cache[p].next;
1270 lla_insert(i, p);
1275 Insert the item at index i before the one at index p in the list starting at
1276 *head
1278 static inline void lla_insert_before(int *head, int i, int p)
1280 lla_insert(i, p);
1281 if (*head == p)
1282 *head = i;
1287 Free the used slide at index i, and its buffer, and move it to the free
1288 slides list.
1290 static inline void free_slide(int i)
1292 if (cache[i].hid != empty_slide_hid)
1293 buflib_free(&buf_ctx, cache[i].hid);
1294 cache[i].index = -1;
1295 lla_pop_item(&cache_used, i);
1296 lla_insert_tail(&cache_free, i);
1297 if (cache_used == -1)
1299 cache_right_index = -1;
1300 cache_left_index = -1;
1301 cache_center_index = -1;
1307 Free one slide ranked above the given priority. If no such slide can be found,
1308 return false.
1310 static bool free_slide_prio(int prio)
1312 if (cache_used == -1)
1313 return false;
1314 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1315 int prio_l = cache[l].index < center_index ?
1316 center_index - cache[l].index : 0;
1317 int prio_r = cache[r].index > center_index ?
1318 cache[r].index - center_index : 0;
1319 if (prio_l > prio_r)
1321 i = l;
1322 prio_max = prio_l;
1323 } else {
1324 i = r;
1325 prio_max = prio_r;
1327 if (prio_max > prio)
1329 if (i == cache_left_index)
1330 cache_left_index = cache[i].next;
1331 if (i == cache_right_index)
1332 cache_right_index = cache[i].prev;
1333 free_slide(i);
1334 return true;
1335 } else
1336 return false;
1340 Read the pfraw image given as filename and return the hid of the buffer
1342 int read_pfraw(char* filename, int prio)
1344 struct pfraw_header bmph;
1345 int fh = rb->open(filename, O_RDONLY);
1346 if( fh < 0 )
1347 return empty_slide_hid;
1348 else
1349 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1351 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1352 bmph.width * bmph.height;
1354 int hid;
1355 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1357 if (!hid) {
1358 rb->close( fh );
1359 return 0;
1362 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1364 bm->width = bmph.width;
1365 bm->height = bmph.height;
1366 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1368 int y;
1369 for( y = 0; y < bm->height; y++ )
1371 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1372 data += bm->width;
1374 rb->close( fh );
1375 return hid;
1380 Load the surface for the given slide_index into the cache at cache_index.
1382 static inline bool load_and_prepare_surface(const int slide_index,
1383 const int cache_index,
1384 const int prio)
1386 char tmp_path_name[MAX_PATH+1];
1387 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1388 slide_index);
1390 int hid = read_pfraw(tmp_path_name, prio);
1391 if (!hid)
1392 return false;
1394 cache[cache_index].hid = hid;
1396 if ( cache_index < SLIDE_CACHE_SIZE ) {
1397 cache[cache_index].index = slide_index;
1400 return true;
1405 Load the "next" slide that we can load, freeing old slides if needed, provided
1406 that they are further from center_index than the current slide
1408 bool load_new_slide(void)
1410 int i = -1;
1411 if (cache_center_index != -1)
1413 int next, prev;
1414 if (cache[cache_center_index].index != center_index)
1416 if (cache[cache_center_index].index < center_index)
1418 cache_center_index = seek_right_while(cache_center_index,
1419 cache[next_].index <= center_index);
1420 prev = cache_center_index;
1421 next = cache[cache_center_index].next;
1423 else
1425 cache_center_index = seek_left_while(cache_center_index,
1426 cache[next_].index >= center_index);
1427 next = cache_center_index;
1428 prev = cache[cache_center_index].prev;
1430 if (cache[cache_center_index].index != center_index)
1432 if (cache_free == -1)
1433 free_slide_prio(0);
1434 i = lla_pop_head(&cache_free);
1435 if (!load_and_prepare_surface(center_index, i, 0))
1436 goto fail_and_refree;
1437 if (cache[next].index == -1)
1439 if (cache[prev].index == -1)
1440 goto insert_first_slide;
1441 else
1442 next = cache[prev].next;
1444 lla_insert(i, next);
1445 if (cache[i].index < cache[cache_used].index)
1446 cache_used = i;
1447 cache_center_index = i;
1448 cache_left_index = i;
1449 cache_right_index = i;
1450 return true;
1453 if (cache[cache_left_index].index >
1454 cache[cache_center_index].index)
1455 cache_left_index = cache_center_index;
1456 if (cache[cache_right_index].index <
1457 cache[cache_center_index].index)
1458 cache_right_index = cache_center_index;
1459 cache_left_index = seek_left_while(cache_left_index,
1460 cache[ind_].index - 1 == cache[next_].index);
1461 cache_right_index = seek_right_while(cache_right_index,
1462 cache[ind_].index - 1 == cache[next_].index);
1463 int prio_l = cache[cache_center_index].index -
1464 cache[cache_left_index].index + 1;
1465 int prio_r = cache[cache_right_index].index -
1466 cache[cache_center_index].index + 1;
1467 if ((prio_l < prio_r ||
1468 cache[cache_right_index].index >= number_of_slides) &&
1469 cache[cache_left_index].index > 0)
1471 if (cache_free == -1 && !free_slide_prio(prio_l))
1472 return false;
1473 i = lla_pop_head(&cache_free);
1474 if (load_and_prepare_surface(cache[cache_left_index].index
1475 - 1, i, prio_l))
1477 lla_insert_before(&cache_used, i, cache_left_index);
1478 cache_left_index = i;
1479 return true;
1481 } else if(cache[cache_right_index].index < number_of_slides - 1)
1483 if (cache_free == -1 && !free_slide_prio(prio_r))
1484 return false;
1485 i = lla_pop_head(&cache_free);
1486 if (load_and_prepare_surface(cache[cache_right_index].index
1487 + 1, i, prio_r))
1489 lla_insert_after(i, cache_right_index);
1490 cache_right_index = i;
1491 return true;
1494 } else {
1495 i = lla_pop_head(&cache_free);
1496 if (load_and_prepare_surface(center_index, i, 0))
1498 insert_first_slide:
1499 cache[i].next = i;
1500 cache[i].prev = i;
1501 cache_center_index = i;
1502 cache_left_index = i;
1503 cache_right_index = i;
1504 cache_used = i;
1505 return true;
1508 fail_and_refree:
1509 if (i != -1)
1511 lla_insert_tail(&cache_free, i);
1513 return false;
1518 Get a slide from the buffer
1520 static inline struct dim *get_slide(const int hid)
1522 if (!hid)
1523 return NULL;
1525 struct dim *bmp;
1527 bmp = buflib_get_data(&buf_ctx, hid);
1529 return bmp;
1534 Return the requested surface
1536 static inline struct dim *surface(const int slide_index)
1538 if (slide_index < 0)
1539 return 0;
1540 if (slide_index >= number_of_slides)
1541 return 0;
1542 int i;
1543 if ((i = cache_used ) != -1)
1545 do {
1546 if (cache[i].index == slide_index)
1547 return get_slide(cache[i].hid);
1548 i = cache[i].next;
1549 } while (i != cache_used);
1551 return get_slide(empty_slide_hid);
1555 adjust slides so that they are in "steady state" position
1557 void reset_slides(void)
1559 center_slide.angle = 0;
1560 center_slide.cx = 0;
1561 center_slide.cy = 0;
1562 center_slide.distance = 0;
1563 center_slide.slide_index = center_index;
1565 int i;
1566 for (i = 0; i < num_slides; i++) {
1567 struct slide_data *si = &left_slides[i];
1568 si->angle = itilt;
1569 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1570 si->cy = offsetY;
1571 si->slide_index = center_index - 1 - i;
1572 si->distance = 0;
1575 for (i = 0; i < num_slides; i++) {
1576 struct slide_data *si = &right_slides[i];
1577 si->angle = -itilt;
1578 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1579 si->cy = offsetY;
1580 si->slide_index = center_index + 1 + i;
1581 si->distance = 0;
1587 Updates look-up table and other stuff necessary for the rendering.
1588 Call this when the viewport size or slide dimension is changed.
1590 * To calculate the offset that will provide the proper margin, we use the same
1591 * projection used to render the slides. The solution for xc, the slide center,
1592 * is:
1593 * xp * (zo + xs * sin(r))
1594 * xc = xp - xs * cos(r) + ───────────────────────
1596 * TODO: support moving the side slides toward or away from the camera
1598 void recalc_offsets(void)
1600 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1601 PFreal zo;
1602 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1603 PFREAL_ONE) * zoom / 100;
1604 PFreal cosr, sinr;
1606 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1607 cosr = fcos(-itilt);
1608 sinr = fsin(-itilt);
1609 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1610 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1611 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1612 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1613 / CAM_DIST;
1614 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1619 Fade the given color by spreading the fb_data (ushort)
1620 to an uint, multiply and compress the result back to a ushort.
1622 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1623 static inline unsigned fade_color(pix_t c, unsigned a)
1625 unsigned int result;
1626 c = swap16(c);
1627 a = (a + 2) & 0x1fc;
1628 result = ((c & 0xf81f) * a) & 0xf81f00;
1629 result |= ((c & 0x7e0) * a) & 0x7e000;
1630 result >>= 8;
1631 return swap16(result);
1633 #elif LCD_PIXELFORMAT == RGB565
1634 static inline unsigned fade_color(pix_t c, unsigned a)
1636 unsigned int result;
1637 a = (a + 2) & 0x1fc;
1638 result = ((c & 0xf81f) * a) & 0xf81f00;
1639 result |= ((c & 0x7e0) * a) & 0x7e000;
1640 result >>= 8;
1641 return result;
1643 #else
1644 static inline unsigned fade_color(pix_t c, unsigned a)
1646 unsigned val = c;
1647 return MULUQ(val, a) >> 8;
1649 #endif
1652 * Render a single slide
1653 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1654 * on the slide from its center, zo is the slide's depth offset from the plane
1655 * of the display, r is the angle at which the slide is tilted, and xp is the
1656 * point on the display corresponding to xs on the slide, the projection
1657 * formulas are:
1659 * z * (xc + xs * cos(r))
1660 * xp = ──────────────────────
1661 * z + zo + xs * sin(r)
1663 * z * (xc - xp) - xp * zo
1664 * xs = ────────────────────────
1665 * xp * sin(r) - z * cos(r)
1667 * We use the xp projection once, to find the left edge of the slide on the
1668 * display. From there, we use the xs reverse projection to find the horizontal
1669 * offset from the slide center of each column on the screen, until we reach
1670 * the right edge of the slide, or the screen. The reverse projection can be
1671 * optimized by saving the numerator and denominator of the fraction, which can
1672 * then be incremented by (z + zo) and sin(r) respectively.
1674 void render_slide(struct slide_data *slide, const int alpha)
1676 struct dim *bmp = surface(slide->slide_index);
1677 if (!bmp) {
1678 return;
1680 if (slide->angle > 255 || slide->angle < -255)
1681 return;
1682 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1684 const int sw = bmp->width;
1685 const int sh = bmp->height;
1686 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1687 const int w = LCD_WIDTH;
1689 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1691 if (alpha == 256) { /* opaque -> copy table */
1692 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1693 } else { /* precalculate faded table */
1694 int i, lalpha;
1695 for (i = 0; i < REFLECT_HEIGHT; i++) {
1696 lalpha = reflect_table[i];
1697 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1701 PFreal cosr = fcos(slide->angle);
1702 PFreal sinr = fsin(slide->angle);
1703 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1704 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1705 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1706 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1707 (CAM_DIST_R + zo + fmul(xs,sinr)));
1709 /* Since we're finding the screen position of the left edge of the slide,
1710 * we round up.
1712 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1713 >> PFREAL_SHIFT;
1714 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1715 if (xi >= w) {
1716 return;
1718 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1719 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1720 xs = fdiv(xsnum, xsden);
1722 xsnumi = -CAM_DIST_R - zo;
1723 xsdeni = sinr;
1724 int x;
1725 int dy = PFREAL_ONE;
1726 for (x = xi; x < w; x++) {
1727 int column = (xs - slide_left) / PFREAL_ONE;
1728 if (column >= sw)
1729 break;
1730 if (zo || slide->angle)
1731 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1733 const pix_t *ptr = &src[column * bmp->height];
1735 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1736 #define PIXELSTEP_Y 1
1737 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1738 #else
1739 #define PIXELSTEP_Y BUFFER_WIDTH
1740 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1741 #endif
1743 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1744 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1745 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1747 if (alpha == 256) {
1748 while (p >= plim) {
1749 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1750 p -= dy;
1751 pixel -= PIXELSTEP_Y;
1753 } else {
1754 while (p >= plim) {
1755 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1756 p -= dy;
1757 pixel -= PIXELSTEP_Y;
1760 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1761 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1762 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1763 p + (LCD_HEIGHT/2) * dy);
1764 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1766 if (alpha == 256) {
1767 while (p < plim) {
1768 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1769 p += dy;
1770 pixel += PIXELSTEP_Y;
1772 } else {
1773 while (p < plim) {
1774 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1775 p += dy;
1776 pixel += PIXELSTEP_Y;
1779 while (p < plim2) {
1780 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1781 int lalpha = reftab[ty];
1782 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1783 p += dy;
1784 pixel += PIXELSTEP_Y;
1787 if (zo || slide->angle)
1789 xsnum += xsnumi;
1790 xsden += xsdeni;
1791 xs = fdiv(xsnum, xsden);
1792 } else
1793 xs += PFREAL_ONE;
1796 /* let the music play... */
1797 rb->yield();
1798 return;
1802 Jump the the given slide_index
1804 static inline void set_current_slide(const int slide_index)
1806 int old_center_index = center_index;
1807 step = 0;
1808 center_index = fbound(slide_index, 0, number_of_slides - 1);
1809 if (old_center_index != center_index)
1810 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1811 target = center_index;
1812 slide_frame = slide_index << 16;
1813 reset_slides();
1817 Start the animation for changing slides
1819 void start_animation(void)
1821 step = (target < center_slide.slide_index) ? -1 : 1;
1822 pf_state = pf_scrolling;
1826 Go to the previous slide
1828 void show_previous_slide(void)
1830 if (step == 0) {
1831 if (center_index > 0) {
1832 target = center_index - 1;
1833 start_animation();
1835 } else if ( step > 0 ) {
1836 target = center_index;
1837 start_animation();
1838 } else {
1839 target = fmax(0, center_index - 2);
1845 Go to the next slide
1847 void show_next_slide(void)
1849 if (step == 0) {
1850 if (center_index < number_of_slides - 1) {
1851 target = center_index + 1;
1852 start_animation();
1854 } else if ( step < 0 ) {
1855 target = center_index;
1856 start_animation();
1857 } else {
1858 target = fmin(center_index + 2, number_of_slides - 1);
1864 Render the slides. Updates only the offscreen buffer.
1866 void render_all_slides(void)
1868 MYLCD(set_background)(G_BRIGHT(0));
1869 /* TODO: Optimizes this by e.g. invalidating rects */
1870 MYLCD(clear_display)();
1872 int nleft = num_slides;
1873 int nright = num_slides;
1875 int index;
1876 if (step == 0) {
1877 /* no animation, boring plain rendering */
1878 for (index = nleft - 2; index >= 0; index--) {
1879 int alpha = (index < nleft - 2) ? 256 : 128;
1880 alpha -= extra_fade;
1881 if (alpha > 0 )
1882 render_slide(&left_slides[index], alpha);
1884 for (index = nright - 2; index >= 0; index--) {
1885 int alpha = (index < nright - 2) ? 256 : 128;
1886 alpha -= extra_fade;
1887 if (alpha > 0 )
1888 render_slide(&right_slides[index], alpha);
1890 } else {
1891 /* the first and last slide must fade in/fade out */
1892 for (index = nleft - 1; index >= 0; index--) {
1893 int alpha = 256;
1894 if (index == nleft - 1)
1895 alpha = (step > 0) ? 0 : 128 - fade / 2;
1896 if (index == nleft - 2)
1897 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1898 if (index == nleft - 3)
1899 alpha = (step > 0) ? 256 - fade / 2 : 256;
1900 render_slide(&left_slides[index], alpha);
1902 for (index = nright - 1; index >= 0; index--) {
1903 int alpha = (index < nright - 2) ? 256 : 128;
1904 if (index == nright - 1)
1905 alpha = (step > 0) ? fade / 2 : 0;
1906 if (index == nright - 2)
1907 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1908 if (index == nright - 3)
1909 alpha = (step > 0) ? 256 : 128 + fade / 2;
1910 render_slide(&right_slides[index], alpha);
1913 render_slide(&center_slide, 256);
1918 Updates the animation effect. Call this periodically from a timer.
1920 void update_scroll_animation(void)
1922 if (step == 0)
1923 return;
1925 int speed = 16384;
1926 int i;
1928 /* deaccelerate when approaching the target */
1929 if (true) {
1930 const int max = 2 * 65536;
1932 int fi = slide_frame;
1933 fi -= (target << 16);
1934 if (fi < 0)
1935 fi = -fi;
1936 fi = fmin(fi, max);
1938 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1939 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1942 slide_frame += speed * step;
1944 int index = slide_frame >> 16;
1945 int pos = slide_frame & 0xffff;
1946 int neg = 65536 - pos;
1947 int tick = (step < 0) ? neg : pos;
1948 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1950 /* the leftmost and rightmost slide must fade away */
1951 fade = pos / 256;
1953 if (step < 0)
1954 index++;
1955 if (center_index != index) {
1956 center_index = index;
1957 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1958 slide_frame = index << 16;
1959 center_slide.slide_index = center_index;
1960 for (i = 0; i < num_slides; i++)
1961 left_slides[i].slide_index = center_index - 1 - i;
1962 for (i = 0; i < num_slides; i++)
1963 right_slides[i].slide_index = center_index + 1 + i;
1966 center_slide.angle = (step * tick * itilt) >> 16;
1967 center_slide.cx = -step * fmul(offsetX, ftick);
1968 center_slide.cy = fmul(offsetY, ftick);
1970 if (center_index == target) {
1971 reset_slides();
1972 pf_state = pf_idle;
1973 step = 0;
1974 fade = 256;
1975 return;
1978 for (i = 0; i < num_slides; i++) {
1979 struct slide_data *si = &left_slides[i];
1980 si->angle = itilt;
1981 si->cx =
1982 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1983 * slide_spacing * ftick);
1984 si->cy = offsetY;
1987 for (i = 0; i < num_slides; i++) {
1988 struct slide_data *si = &right_slides[i];
1989 si->angle = -itilt;
1990 si->cx =
1991 offsetX + slide_spacing * i * PFREAL_ONE - step
1992 * slide_spacing * ftick;
1993 si->cy = offsetY;
1996 if (step > 0) {
1997 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1998 right_slides[0].angle = -(neg * itilt) >> 16;
1999 right_slides[0].cx = fmul(offsetX, ftick);
2000 right_slides[0].cy = fmul(offsetY, ftick);
2001 } else {
2002 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2003 left_slides[0].angle = (pos * itilt) >> 16;
2004 left_slides[0].cx = -fmul(offsetX, ftick);
2005 left_slides[0].cy = fmul(offsetY, ftick);
2008 /* must change direction ? */
2009 if (target < index)
2010 if (step > 0)
2011 step = -1;
2012 if (target > index)
2013 if (step < 0)
2014 step = 1;
2019 Cleanup the plugin
2021 void cleanup(void *parameter)
2023 (void) parameter;
2024 int i;
2025 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2026 rb->cpu_boost(false);
2027 #endif
2028 end_pf_thread();
2029 /* Turn on backlight timeout (revert to settings) */
2030 backlight_use_settings(); /* backlight control in lib/helper.c */
2032 #ifdef USEGSLIB
2033 grey_release();
2034 #endif
2035 FOR_NB_SCREENS(i)
2036 rb->viewportmanager_theme_undo(i, false);
2040 Create the "?" slide, that is shown while loading
2041 or when no cover was found.
2043 int create_empty_slide(bool force)
2045 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2046 struct bitmap input_bmp;
2047 int ret;
2048 input_bmp.width = DISPLAY_WIDTH;
2049 input_bmp.height = DISPLAY_HEIGHT;
2050 #if LCD_DEPTH > 1
2051 input_bmp.format = FORMAT_NATIVE;
2052 #endif
2053 input_bmp.data = (char*)buf;
2054 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2055 buf_size,
2056 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2057 &format_transposed);
2058 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2059 return false;
2062 return true;
2066 Shows the settings menu
2068 int settings_menu(void)
2070 int selection = 0;
2071 bool old_val;
2073 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2074 "Spacing", "Centre margin", "Number of slides", "Zoom",
2075 "Show album title", "Resize Covers", "Rebuild cache");
2077 static const struct opt_items album_name_options[] = {
2078 { "Hide album title", -1 },
2079 { "Show at the bottom", -1 },
2080 { "Show at the top", -1 }
2083 do {
2084 selection=rb->do_menu(&settings_menu,&selection, NULL, true);
2085 switch(selection) {
2086 case 0:
2087 rb->set_bool("Show FPS", &show_fps);
2088 reset_track_list();
2089 break;
2091 case 1:
2092 rb->set_int("Spacing between slides", "", 1,
2093 &slide_spacing,
2094 NULL, 1, 0, 100, NULL );
2095 recalc_offsets();
2096 reset_slides();
2097 break;
2099 case 2:
2100 rb->set_int("Centre margin", "", 1,
2101 &center_margin,
2102 NULL, 1, 0, 80, NULL );
2103 recalc_offsets();
2104 reset_slides();
2105 break;
2107 case 3:
2108 rb->set_int("Number of slides", "", 1, &num_slides,
2109 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2110 recalc_offsets();
2111 reset_slides();
2112 break;
2114 case 4:
2115 rb->set_int("Zoom", "", 1, &zoom,
2116 NULL, 1, 10, 300, NULL );
2117 recalc_offsets();
2118 reset_slides();
2119 break;
2120 case 5:
2121 rb->set_option("Show album title", &show_album_name,
2122 INT, album_name_options, 3, NULL);
2123 reset_track_list();
2124 recalc_offsets();
2125 reset_slides();
2126 break;
2127 case 6:
2128 old_val = resize;
2129 rb->set_bool("Resize Covers", &resize);
2130 if (old_val == resize) /* changed? */
2131 break;
2132 /* fallthrough if changed, since cache needs to be rebuilt */
2133 case 7:
2134 cache_version = 0;
2135 rb->remove(EMPTY_SLIDE);
2136 rb->splash(HZ, "Cache will be rebuilt on next restart");
2137 break;
2139 case MENU_ATTACHED_USB:
2140 return PLUGIN_USB_CONNECTED;
2142 } while ( selection >= 0 );
2143 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2144 return 0;
2148 Show the main menu
2150 enum {
2151 PF_GOTO_WPS,
2152 #if PF_PLAYBACK_CAPABLE
2153 PF_MENU_PLAYBACK_CONTROL,
2154 #endif
2155 PF_MENU_SETTINGS,
2156 PF_MENU_RETURN,
2157 PF_MENU_QUIT,
2160 int main_menu(void)
2162 int selection = 0;
2163 int result;
2165 #if LCD_DEPTH > 1
2166 rb->lcd_set_foreground(N_BRIGHT(255));
2167 #endif
2169 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2170 "Go to WPS",
2171 #if PF_PLAYBACK_CAPABLE
2172 "Playback Control",
2173 #endif
2174 "Settings", "Return", "Quit");
2175 while (1) {
2176 switch (rb->do_menu(&main_menu,&selection, NULL, true)) {
2177 case PF_GOTO_WPS: /* WPS */
2178 return -2;
2179 #if PF_PLAYBACK_CAPABLE
2180 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2181 playback_control(NULL);
2182 break;
2183 #endif
2184 case PF_MENU_SETTINGS:
2185 result = settings_menu();
2186 if ( result != 0 ) return result;
2187 break;
2188 case PF_MENU_RETURN:
2189 return 0;
2190 case PF_MENU_QUIT:
2191 return -1;
2193 case MENU_ATTACHED_USB:
2194 return PLUGIN_USB_CONNECTED;
2196 default:
2197 return 0;
2203 Animation step for zooming into the current cover
2205 void update_cover_in_animation(void)
2207 cover_animation_keyframe++;
2208 if( cover_animation_keyframe < 20 ) {
2209 center_slide.distance-=5;
2210 center_slide.angle+=1;
2211 extra_fade += 13;
2213 else if( cover_animation_keyframe < 35 ) {
2214 center_slide.angle+=16;
2216 else {
2217 cover_animation_keyframe = 0;
2218 pf_state = pf_show_tracks;
2223 Animation step for zooming out the current cover
2225 void update_cover_out_animation(void)
2227 cover_animation_keyframe++;
2228 if( cover_animation_keyframe <= 15 ) {
2229 center_slide.angle-=16;
2231 else if( cover_animation_keyframe < 35 ) {
2232 center_slide.distance+=5;
2233 center_slide.angle-=1;
2234 extra_fade -= 13;
2236 else {
2237 cover_animation_keyframe = 0;
2238 pf_state = pf_idle;
2243 Draw a blue gradient at y with height h
2245 static inline void draw_gradient(int y, int h)
2247 static int r, inc, c;
2248 inc = (100 << 8) / h;
2249 c = 0;
2250 selected_track_pulse = (selected_track_pulse+1) % 10;
2251 int c2 = selected_track_pulse - 5;
2252 for (r=0; r<h; r++) {
2253 #ifdef HAVE_LCD_COLOR
2254 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2255 c2+250-(c >> 8)));
2256 #else
2257 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2258 #endif
2259 MYLCD(hline)(0, LCD_WIDTH, r+y);
2260 if ( r > h/2 )
2261 c-=inc;
2262 else
2263 c+=inc;
2268 static void track_list_yh(int char_height)
2270 switch (show_album_name)
2272 case album_name_hide:
2273 track_list_y = (show_fps ? char_height : 0);
2274 track_list_h = LCD_HEIGHT - track_list_y;
2275 break;
2276 case album_name_bottom:
2277 track_list_y = (show_fps ? char_height : 0);
2278 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2279 break;
2280 default: /* case album_name_top */
2281 track_list_y = char_height * 2;
2282 track_list_h = LCD_HEIGHT - track_list_y -
2283 (show_fps ? char_height : 0);
2284 break;
2289 Reset the track list after a album change
2291 void reset_track_list(void)
2293 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2294 track_list_yh(albumtxt_h);
2295 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2296 start_index_track_list = 0;
2297 track_scroll_index = 0;
2298 track_scroll_dir = 1;
2299 selected_track = 0;
2301 /* let the tracklist start more centered
2302 * if the screen isn't filled with tracks */
2303 if (track_count*albumtxt_h < track_list_h)
2305 track_list_h = track_count * albumtxt_h;
2306 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2311 Display the list of tracks
2313 void show_track_list(void)
2315 MYLCD(clear_display)();
2316 if ( center_slide.slide_index != track_index ) {
2317 create_track_index(center_slide.slide_index);
2318 reset_track_list();
2320 static int titletxt_w, titletxt_x, color, titletxt_h;
2321 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2323 int titletxt_y = track_list_y;
2324 int track_i;
2325 track_i = start_index_track_list;
2326 for (;track_i < track_list_visible_entries+start_index_track_list;
2327 track_i++)
2329 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2330 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2331 if ( track_i == selected_track ) {
2332 draw_gradient(titletxt_y, titletxt_h);
2333 MYLCD(set_foreground)(G_BRIGHT(255));
2334 if (titletxt_w > LCD_WIDTH ) {
2335 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2336 track_scroll_dir = 1;
2337 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2338 track_scroll_index += track_scroll_dir*2;
2339 titletxt_x = track_scroll_index;
2341 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2343 else {
2344 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2345 MYLCD(set_foreground)(G_BRIGHT(color));
2346 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2348 titletxt_y += titletxt_h;
2352 void select_next_track(void)
2354 if ( selected_track < track_count - 1 ) {
2355 selected_track++;
2356 track_scroll_index = 0;
2357 track_scroll_dir = 1;
2358 if (selected_track==(track_list_visible_entries+start_index_track_list))
2359 start_index_track_list++;
2363 void select_prev_track(void)
2365 if (selected_track > 0 ) {
2366 if (selected_track==start_index_track_list) start_index_track_list--;
2367 track_scroll_index = 0;
2368 track_scroll_dir = 1;
2369 selected_track--;
2373 #if PF_PLAYBACK_CAPABLE
2375 * Puts the current tracklist into a newly created playlist and starts playling
2377 void start_playback(void)
2379 static int old_playlist = -1, old_shuffle = 0;
2380 int count = 0;
2381 int position = selected_track;
2382 int shuffle = rb->global_settings->playlist_shuffle;
2383 /* reuse existing playlist if possible
2384 * regenerate if shuffle is on or changed, since playlist index and
2385 * selected track are "out of sync" */
2386 if (!shuffle && center_slide.slide_index == old_playlist
2387 && (old_shuffle == shuffle))
2389 goto play;
2391 /* First, replace the current playlist with a new one */
2392 else if (rb->playlist_remove_all_tracks(NULL) == 0
2393 && rb->playlist_create(NULL, NULL) == 0)
2395 do {
2396 rb->yield();
2397 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2398 PLAYLIST_INSERT_LAST, false, true) < 0)
2399 break;
2400 } while(++count < track_count);
2401 rb->playlist_sync(NULL);
2403 else
2404 return;
2406 if (rb->global_settings->playlist_shuffle)
2407 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2408 play:
2409 /* TODO: can we adjust selected_track if !play_selected ?
2410 * if shuffle, we can't predict the playing track easily, and for either
2411 * case the track list doesn't get auto scrolled*/
2412 rb->playlist_start(position, 0);
2413 old_playlist = center_slide.slide_index;
2414 old_shuffle = shuffle;
2416 #endif
2419 Draw the current album name
2421 void draw_album_text(void)
2423 if (0 == show_album_name)
2424 return;
2426 int albumtxt_w, albumtxt_h;
2427 int albumtxt_y = 0;
2429 char *albumtxt;
2430 int c;
2431 /* Draw album text */
2432 if ( pf_state == pf_scrolling ) {
2433 c = ((slide_frame & 0xffff )/ 255);
2434 if (step < 0) c = 255-c;
2435 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2436 albumtxt = get_album_name(center_index+step);
2437 c = (c-128)*2;
2439 else {
2440 albumtxt = get_album_name(center_index);
2441 c = (128-c)*2;
2444 else {
2445 c= 255;
2446 albumtxt = get_album_name(center_index);
2449 MYLCD(set_foreground)(G_BRIGHT(c));
2450 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2451 if (center_index != prev_center_index) {
2452 albumtxt_x = 0;
2453 albumtxt_dir = -1;
2454 prev_center_index = center_index;
2457 if (show_album_name == album_name_top)
2458 albumtxt_y = albumtxt_h / 2;
2459 else
2460 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2462 if (albumtxt_w > LCD_WIDTH ) {
2463 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2464 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2465 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2466 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2467 albumtxt_x += albumtxt_dir;
2470 else {
2471 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2478 Display an error message and wait for input.
2480 void error_wait(const char *message)
2482 rb->splashf(0, "%s. Press any button to continue.", message);
2483 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2484 rb->yield();
2485 rb->sleep(2 * HZ);
2489 Main function that also contain the main plasma
2490 algorithm.
2492 int main(void)
2494 int ret;
2496 rb->lcd_setfont(FONT_UI);
2497 draw_splashscreen();
2499 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2500 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2501 error_wait("Could not create directory " CACHE_PREFIX);
2502 return PLUGIN_ERROR;
2506 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2508 init_reflect_table();
2510 ALIGN_BUFFER(buf, buf_size, 4);
2511 ret = create_album_index();
2512 if (ret == ERROR_BUFFER_FULL) {
2513 error_wait("Not enough memory for album names");
2514 return PLUGIN_ERROR;
2515 } else if (ret == ERROR_NO_ALBUMS) {
2516 error_wait("No albums found. Please enable database");
2517 return PLUGIN_ERROR;
2520 ALIGN_BUFFER(buf, buf_size, 4);
2521 number_of_slides = album_count;
2522 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2523 error_wait("Could not create album art cache");
2524 return PLUGIN_ERROR;
2527 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2528 error_wait("Could not load the empty slide");
2529 return PLUGIN_ERROR;
2531 cache_version = CACHE_VERSION;
2532 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2535 #ifdef USEGSLIB
2536 long grey_buf_used;
2537 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2538 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2540 error_wait("Greylib init failed!");
2541 return PLUGIN_ERROR;
2543 grey_setfont(FONT_UI);
2544 buf_size -= grey_buf_used;
2545 buf = (void*)(grey_buf_used + (char*)buf);
2546 #endif
2547 buflib_init(&buf_ctx, (void *)buf, buf_size);
2549 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2551 error_wait("Unable to load empty slide image");
2552 return PLUGIN_ERROR;
2555 if (!create_pf_thread()) {
2556 error_wait("Cannot create thread!");
2557 return PLUGIN_ERROR;
2560 int i;
2562 /* initialize */
2563 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2564 cache[i].hid = 0;
2565 cache[i].index = 0;
2566 cache[i].next = i + 1;
2567 cache[i].prev = i - 1;
2569 cache[0].prev = i - 1;
2570 cache[i - 1].next = 0;
2571 cache_free = 0;
2572 buffer = LCD_BUF;
2574 pf_state = pf_idle;
2576 track_index = -1;
2577 extra_fade = 0;
2578 slide_frame = 0;
2579 step = 0;
2580 target = 0;
2581 fade = 256;
2583 recalc_offsets();
2584 reset_slides();
2586 char fpstxt[10];
2587 int button;
2589 int frames = 0;
2590 long last_update = *rb->current_tick;
2591 long current_update;
2592 long update_interval = 100;
2593 int fps = 0;
2594 int fpstxt_y;
2596 bool instant_update;
2597 #ifdef USEGSLIB
2598 grey_show(true);
2599 grey_set_drawmode(DRMODE_FG);
2600 #endif
2601 rb->lcd_set_drawmode(DRMODE_FG);
2602 while (true) {
2603 current_update = *rb->current_tick;
2604 frames++;
2606 /* Initial rendering */
2607 instant_update = false;
2609 /* Handle states */
2610 switch ( pf_state ) {
2611 case pf_scrolling:
2612 update_scroll_animation();
2613 render_all_slides();
2614 instant_update = true;
2615 break;
2616 case pf_cover_in:
2617 update_cover_in_animation();
2618 render_all_slides();
2619 instant_update = true;
2620 break;
2621 case pf_cover_out:
2622 update_cover_out_animation();
2623 render_all_slides();
2624 instant_update = true;
2625 break;
2626 case pf_show_tracks:
2627 show_track_list();
2628 break;
2629 case pf_idle:
2630 render_all_slides();
2631 break;
2634 /* Calculate FPS */
2635 if (current_update - last_update > update_interval) {
2636 fps = frames * HZ / (current_update - last_update);
2637 last_update = current_update;
2638 frames = 0;
2640 /* Draw FPS */
2641 if (show_fps)
2643 #ifdef USEGSLIB
2644 MYLCD(set_foreground)(G_BRIGHT(255));
2645 #else
2646 MYLCD(set_foreground)(G_PIX(255,0,0));
2647 #endif
2648 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2649 if (show_album_name == album_name_top)
2650 fpstxt_y = LCD_HEIGHT -
2651 rb->screens[SCREEN_MAIN]->getcharheight();
2652 else
2653 fpstxt_y = 0;
2654 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2656 draw_album_text();
2659 /* Copy offscreen buffer to LCD and give time to other threads */
2660 MYLCD(update)();
2661 rb->yield();
2663 /*/ Handle buttons */
2664 button = rb->get_custom_action(CONTEXT_PLUGIN
2665 #ifndef USE_CORE_PREVNEXT
2666 |(pf_state == pf_show_tracks ? 1 : 0)
2667 #endif
2668 ,instant_update ? 0 : HZ/16,
2669 get_context_map);
2671 switch (button) {
2672 case PF_QUIT:
2673 return PLUGIN_OK;
2674 case PF_WPS:
2675 return PLUGIN_GOTO_WPS;
2676 case PF_BACK:
2677 if ( pf_state == pf_show_tracks )
2679 buflib_buffer_in(&buf_ctx, borrowed);
2680 borrowed = 0;
2681 track_index = -1;
2682 pf_state = pf_cover_out;
2684 if (pf_state == pf_idle || pf_state == pf_scrolling)
2685 return PLUGIN_OK;
2686 break;
2687 case PF_MENU:
2688 #ifdef USEGSLIB
2689 grey_show(false);
2690 #endif
2691 ret = main_menu();
2692 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2693 if ( ret == -1 ) return PLUGIN_OK;
2694 if ( ret != 0 ) return ret;
2695 #ifdef USEGSLIB
2696 grey_show(true);
2697 #endif
2698 MYLCD(set_drawmode)(DRMODE_FG);
2699 break;
2701 case PF_NEXT:
2702 case PF_NEXT_REPEAT:
2703 if ( pf_state == pf_show_tracks )
2704 select_next_track();
2705 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2706 show_next_slide();
2707 break;
2709 case PF_PREV:
2710 case PF_PREV_REPEAT:
2711 if ( pf_state == pf_show_tracks )
2712 select_prev_track();
2713 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2714 show_previous_slide();
2715 break;
2717 case PF_SELECT:
2718 if ( pf_state == pf_idle ) {
2719 pf_state = pf_cover_in;
2721 else if ( pf_state == pf_show_tracks ) {
2722 #if PF_PLAYBACK_CAPABLE
2723 start_playback();
2724 #endif
2726 break;
2727 default:
2728 if (rb->default_event_handler_ex(button, cleanup, NULL)
2729 == SYS_USB_CONNECTED)
2730 return PLUGIN_USB_CONNECTED;
2731 break;
2736 /*************************** Plugin entry point ****************************/
2738 enum plugin_status plugin_start(const void *parameter)
2740 int ret, i;
2741 (void) parameter;
2743 FOR_NB_SCREENS(i)
2744 rb->viewportmanager_theme_enable(i, false, NULL);
2745 /* Turn off backlight timeout */
2746 backlight_force_on(); /* backlight control in lib/helper.c */
2747 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2748 rb->cpu_boost(true);
2749 #endif
2750 #if PF_PLAYBACK_CAPABLE
2751 buf = rb->plugin_get_buffer(&buf_size);
2752 #else
2753 buf = rb->plugin_get_audio_buffer(&buf_size);
2754 #ifndef SIMULATOR
2755 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2757 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2758 buf_size = MIN(buf_size, tmp_size);
2760 #endif
2761 #endif
2762 ret = main();
2763 if ( ret == PLUGIN_OK ) {
2764 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2765 CONFIG_VERSION))
2767 rb->splash(HZ, "Error writing config.");
2768 ret = PLUGIN_ERROR;
2772 cleanup(NULL);
2773 return ret;