Introduce __attribute__((unused)) (#defined to UNUSED_ATTR) to mark possibly unused...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob1e5e2b06c07d4cb2e0238ab52494f4b0fd770786
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_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
109 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
110 #endif
111 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
112 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
113 #elif CONFIG_KEYPAD == SANSA_C100_PAD
114 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
115 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
116 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
117 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
118 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
119 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
120 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
121 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
122 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
123 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
125 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
126 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
127 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
128 #if CONFIG_KEYPAD == COWOND2_PAD
129 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
130 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
131 #endif
132 #elif CONFIG_KEYPAD == SANSA_E200_PAD
133 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
134 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
135 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
136 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
137 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
138 || (CONFIG_KEYPAD == IPOD_4G_PAD)
139 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
140 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
141 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
142 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
143 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
144 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
145 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
146 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
147 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
148 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
149 #endif
150 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
151 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
152 #else
153 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
154 #endif
156 const struct button_mapping *pf_contexts[] =
158 #ifndef USE_CORE_PREVNEXT
159 pf_context_album_scroll,
160 #endif
161 pf_context_buttons
164 #if LCD_DEPTH < 8
165 #if LCD_DEPTH > 1
166 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
167 #else /* LCD_DEPTH <= 1 */
168 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
169 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
170 #define PICTUREFLOW_DRMODE DRMODE_SOLID
171 #else
172 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
173 #endif
174 #endif /* LCD_DEPTH <= 1 */
175 #define USEGSLIB
176 GREY_INFO_STRUCT
177 #define LCD_BUF _grey_info.buffer
178 #define MYLCD(fn) grey_ ## fn
179 #define G_PIX(r,g,b) \
180 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
181 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
182 #define G_BRIGHT(y) (y)
183 #define BUFFER_WIDTH _grey_info.width
184 #define BUFFER_HEIGHT _grey_info.height
185 typedef unsigned char pix_t;
186 #else /* LCD_DEPTH >= 8 */
187 #define LCD_BUF rb->lcd_framebuffer
188 #define MYLCD(fn) rb->lcd_ ## fn
189 #define G_PIX LCD_RGBPACK
190 #define N_PIX LCD_RGBPACK
191 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
192 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
193 #define BUFFER_WIDTH LCD_WIDTH
194 #define BUFFER_HEIGHT LCD_HEIGHT
195 typedef fb_data pix_t;
196 #endif /* LCD_DEPTH >= 8 */
198 /* for fixed-point arithmetic, we need minimum 32-bit long
199 long long (64-bit) might be useful for multiplication and division */
200 #define PFreal long
201 #define PFREAL_SHIFT 10
202 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
203 #define PFREAL_ONE (1 << PFREAL_SHIFT)
204 #define PFREAL_HALF (PFREAL_ONE >> 1)
207 #define IANGLE_MAX 1024
208 #define IANGLE_MASK 1023
210 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
211 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
212 #define DISPLAY_HEIGHT REFLECT_TOP
213 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
214 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
215 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
216 (REFLECT_HEIGHT * 5))
217 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
218 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
219 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
220 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
221 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
223 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
225 #define MAX_SLIDES_COUNT 10
227 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
228 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
230 #define EV_EXIT 9999
231 #define EV_WAKEUP 1337
233 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
234 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
235 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
237 /* Error return values */
238 #define ERROR_NO_ALBUMS -1
239 #define ERROR_BUFFER_FULL -2
241 /* current version for cover cache */
242 #define CACHE_VERSION 3
243 #define CONFIG_VERSION 1
244 #define CONFIG_FILE "pictureflow.cfg"
246 /** structs we use */
248 struct slide_data {
249 int slide_index;
250 int angle;
251 PFreal cx;
252 PFreal cy;
253 PFreal distance;
256 struct slide_cache {
257 int index; /* index of the cached slide */
258 int hid; /* handle ID of the cached slide */
259 short next; /* "next" slide, with LRU last */
260 short prev; /* "previous" slide */
263 struct album_data {
264 int name_idx;
265 long seek;
268 struct track_data {
269 uint32_t sort;
270 int name_idx; /* offset to the track name */
271 long seek;
272 #if PF_PLAYBACK_CAPABLE
273 /* offset to the filename in the string, needed for playlist generation */
274 int filename_idx;
275 #endif
278 struct rect {
279 int left;
280 int right;
281 int top;
282 int bottom;
285 struct load_slide_event_data {
286 int slide_index;
287 int cache_index;
291 struct pfraw_header {
292 int32_t width; /* bmap width in pixels */
293 int32_t height; /* bmap height in pixels */
296 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
297 album_name_top };
298 static char* show_album_name_conf[] =
300 "hide",
301 "bottom",
302 "top"
305 #define MAX_SPACING 40
306 #define MAX_MARGIN 80
308 /* config values and their defaults */
309 static int slide_spacing = DISPLAY_WIDTH / 4;
310 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
311 static int num_slides = 4;
312 static int zoom = 100;
313 static bool show_fps = false;
314 static bool resize = true;
315 static int cache_version = 0;
316 static int show_album_name = (LCD_HEIGHT > 100)
317 ? album_name_top : album_name_bottom;
319 static struct configdata config[] =
321 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
322 NULL },
323 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
324 NULL },
325 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
326 NULL },
327 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
328 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
329 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
330 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
331 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
332 show_album_name_conf }
335 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
337 /** below we allocate the memory we want to use **/
339 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
340 static uint8_t reflect_table[REFLECT_HEIGHT];
341 static struct slide_data center_slide;
342 static struct slide_data left_slides[MAX_SLIDES_COUNT];
343 static struct slide_data right_slides[MAX_SLIDES_COUNT];
344 static int slide_frame;
345 static int step;
346 static int target;
347 static int fade;
348 static int center_index = 0; /* index of the slide that is in the center */
349 static int itilt;
350 static PFreal offsetX;
351 static PFreal offsetY;
352 static int number_of_slides;
354 static struct slide_cache cache[SLIDE_CACHE_SIZE];
355 static int cache_free;
356 static int cache_used = -1;
357 static int cache_left_index = -1;
358 static int cache_right_index = -1;
359 static int cache_center_index = -1;
361 /* use long for aligning */
362 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
363 /* queue (as array) for scheduling load_surface */
365 static int empty_slide_hid;
367 unsigned int thread_id;
368 struct event_queue thread_q;
370 static struct tagcache_search tcs;
372 static struct buflib_context buf_ctx;
374 static struct album_data *album;
375 static char *album_names;
376 static int album_count;
378 static struct track_data *tracks;
379 static char *track_names;
380 static size_t borrowed = 0;
381 static int track_count;
382 static int track_index;
383 static int selected_track;
384 static int selected_track_pulse;
385 void reset_track_list(void);
387 void * buf;
388 size_t buf_size;
390 static bool thread_is_running;
392 static int cover_animation_keyframe;
393 static int extra_fade;
395 static int albumtxt_x = 0;
396 static int albumtxt_dir = -1;
397 static int prev_center_index = -1;
399 static int start_index_track_list = 0;
400 static int track_list_visible_entries = 0;
401 static int track_list_y;
402 static int track_list_h;
403 static int track_scroll_index = 0;
404 static int track_scroll_dir = 1;
407 Proposals for transitions:
409 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
410 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
412 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
414 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
416 TODO:
417 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
418 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
420 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
422 enum pf_states {
423 pf_idle = 0,
424 pf_scrolling,
425 pf_cover_in,
426 pf_show_tracks,
427 pf_cover_out
430 static int pf_state;
432 /** code */
433 static bool free_slide_prio(int prio);
434 static inline unsigned fade_color(pix_t c, unsigned a);
435 bool save_pfraw(char* filename, struct bitmap *bm);
436 bool load_new_slide(void);
437 int load_surface(int);
439 static inline PFreal fmul(PFreal a, PFreal b)
441 return (a*b) >> PFREAL_SHIFT;
445 * This version preshifts each operand, which is useful when we know how many
446 * of the least significant bits will be empty, or are worried about overflow
447 * in a particular calculation
449 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
451 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
454 /* ARMv5+ has a clz instruction equivalent to our function.
456 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
457 static inline int clz(uint32_t v)
459 return __builtin_clz(v);
462 /* Otherwise, use our clz, which can be inlined */
463 #elif defined(CPU_COLDFIRE)
464 /* This clz is based on the log2(n) implementation at
465 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
466 * A clz benchmark plugin showed this to be about 14% faster on coldfire
467 * than the LUT-based version.
469 static inline int clz(uint32_t v)
471 int r = 32;
472 if (v >= 0x10000)
474 v >>= 16;
475 r -= 16;
477 if (v & 0xff00)
479 v >>= 8;
480 r -= 8;
482 if (v & 0xf0)
484 v >>= 4;
485 r -= 4;
487 if (v & 0xc)
489 v >>= 2;
490 r -= 2;
492 if (v & 2)
494 v >>= 1;
495 r -= 1;
497 r -= v;
498 return r;
500 #else
501 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
502 0, 0, 0, 0, 0, 0, 0, 0 };
503 /* This clz is based on the log2(n) implementation at
504 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
505 * It is not any faster than the one above, but trades 16B in the lookup table
506 * for a savings of 12B per each inlined call.
508 static inline int clz(uint32_t v)
510 int r = 28;
511 if (v >= 0x10000)
513 v >>= 16;
514 r -= 16;
516 if (v & 0xff00)
518 v >>= 8;
519 r -= 8;
521 if (v & 0xf0)
523 v >>= 4;
524 r -= 4;
526 return r + clz_lut[v];
528 #endif
530 /* Return the maximum possible left shift for a signed int32, without
531 * overflow
533 static inline int allowed_shift(int32_t val)
535 uint32_t uval = val ^ (val >> 31);
536 return clz(uval) - 1;
539 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
540 * num and den before dividing.
542 static inline PFreal fdiv(PFreal num, PFreal den)
544 int shift = allowed_shift(num);
545 shift = MIN(PFREAL_SHIFT, shift);
546 num <<= shift;
547 den >>= PFREAL_SHIFT - shift;
548 return num / den;
551 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
552 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
553 #define fabs(a) (a < 0 ? -a : a)
554 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
556 #if CONFIG_CPU == SH7034
557 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
558 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
559 #else
560 #define MULUQ(a, b) ((a) * (b))
561 #endif
564 #if 0
565 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
566 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
568 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
569 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
571 static inline PFreal fmul(PFreal a, PFreal b)
573 return (a*b) >> PFREAL_SHIFT;
576 static inline PFreal fdiv(PFreal n, PFreal m)
578 return (n<<(PFREAL_SHIFT))/m;
580 #endif
582 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
583 static const short sin_tab[] = {
584 0, 100, 200, 297, 392, 483, 569, 650,
585 724, 792, 851, 903, 946, 980, 1004, 1019,
586 1024, 1019, 1004, 980, 946, 903, 851, 792,
587 724, 650, 569, 483, 392, 297, 200, 100,
588 0, -100, -200, -297, -392, -483, -569, -650,
589 -724, -792, -851, -903, -946, -980, -1004, -1019,
590 -1024, -1019, -1004, -980, -946, -903, -851, -792,
591 -724, -650, -569, -483, -392, -297, -200, -100,
595 static inline PFreal fsin(int iangle)
597 iangle &= IANGLE_MASK;
599 int i = (iangle >> 4);
600 PFreal p = sin_tab[i];
601 PFreal q = sin_tab[(i+1)];
602 PFreal g = (q - p);
603 return p + g * (iangle-i*16)/16;
606 static inline PFreal fcos(int iangle)
608 return fsin(iangle + (IANGLE_MAX >> 2));
611 static inline unsigned scale_val(unsigned val, unsigned bits)
613 val = val * ((1 << bits) - 1);
614 return ((val >> 8) + val + 128) >> 8;
617 static void output_row_8_transposed(uint32_t row, void * row_in,
618 struct scaler_context *ctx)
620 pix_t *dest = (pix_t*)ctx->bm->data + row;
621 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
622 #ifdef USEGSLIB
623 uint8_t *qp = (uint8_t*)row_in;
624 for (; dest < end; dest += ctx->bm->height)
625 *dest = *qp++;
626 #else
627 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
628 unsigned r, g, b;
629 for (; dest < end; dest += ctx->bm->height)
631 r = scale_val(qp->red, 5);
632 g = scale_val(qp->green, 6);
633 b = scale_val((qp++)->blue, 5);
634 *dest = LCD_RGBPACK_LCD(r,g,b);
636 #endif
639 static void output_row_32_transposed(uint32_t row, void * row_in,
640 struct scaler_context *ctx)
642 pix_t *dest = (pix_t*)ctx->bm->data + row;
643 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
644 #ifdef USEGSLIB
645 uint32_t *qp = (uint32_t*)row_in;
646 for (; dest < end; dest += ctx->bm->height)
647 *dest = SC_OUT(*qp++, ctx);
648 #else
649 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
650 int r, g, b;
651 for (; dest < end; dest += ctx->bm->height)
653 r = scale_val(SC_OUT(qp->r, ctx), 5);
654 g = scale_val(SC_OUT(qp->g, ctx), 6);
655 b = scale_val(SC_OUT(qp->b, ctx), 5);
656 qp++;
657 *dest = LCD_RGBPACK_LCD(r,g,b);
659 #endif
662 #ifdef HAVE_LCD_COLOR
663 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
664 struct scaler_context *ctx)
666 pix_t *dest = (pix_t*)ctx->bm->data + row;
667 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
668 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
669 for (; dest < end; dest += ctx->bm->height)
671 unsigned r, g, b, y, u, v;
672 y = SC_OUT(qp->b, ctx);
673 u = SC_OUT(qp->g, ctx);
674 v = SC_OUT(qp->r, ctx);
675 qp++;
676 yuv_to_rgb(y, u, v, &r, &g, &b);
677 r = scale_val(r, 5);
678 g = scale_val(g, 6);
679 b = scale_val(b, 5);
680 *dest = LCD_RGBPACK_LCD(r, g, b);
683 #endif
685 static unsigned int get_size(struct bitmap *bm)
687 return bm->width * bm->height * sizeof(pix_t);
690 const struct custom_format format_transposed = {
691 .output_row_8 = output_row_8_transposed,
692 #ifdef HAVE_LCD_COLOR
693 .output_row_32 = {
694 output_row_32_transposed,
695 output_row_32_transposed_fromyuv
697 #else
698 .output_row_32 = output_row_32_transposed,
699 #endif
700 .get_size = get_size
703 static const struct button_mapping* get_context_map(int context)
705 return pf_contexts[context & ~CONTEXT_PLUGIN];
708 /* Create the lookup table with the scaling values for the reflections */
709 void init_reflect_table(void)
711 int i;
712 for (i = 0; i < REFLECT_HEIGHT; i++)
713 reflect_table[i] =
714 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
715 (5 * REFLECT_HEIGHT);
719 Create an index of all albums from the database.
720 Also store the album names so we can access them later.
722 int create_album_index(void)
724 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
725 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
726 album_count = 0;
727 rb->tagcache_search(&tcs, tag_album);
728 unsigned int l, old_l = 0;
729 album_names = buf;
730 album[0].name_idx = 0;
731 while (rb->tagcache_get_next(&tcs))
733 buf_size -= sizeof(struct album_data);
734 l = tcs.result_len;
735 if ( album_count > 0 )
736 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
738 if ( l > buf_size )
739 /* not enough memory */
740 return ERROR_BUFFER_FULL;
742 rb->strcpy(buf, tcs.result);
743 buf_size -= l;
744 buf = l + (char *)buf;
745 album[-album_count].seek = tcs.result_seek;
746 old_l = l;
747 album_count++;
749 rb->tagcache_search_finish(&tcs);
750 ALIGN_BUFFER(buf, buf_size, 4);
751 int i;
752 struct album_data* tmp_album = (struct album_data*)buf;
753 for (i = album_count - 1; i >= 0; i--)
754 tmp_album[i] = album[-i];
755 album = tmp_album;
756 buf = album + album_count;
757 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
761 Return a pointer to the album name of the given slide_index
763 char* get_album_name(const int slide_index)
765 return album_names + album[slide_index].name_idx;
769 Return a pointer to the track name of the active album
770 create_track_index has to be called first.
772 char* get_track_name(const int track_index)
774 if ( track_index < track_count )
775 return track_names + tracks[track_index].name_idx;
776 return 0;
778 #if PF_PLAYBACK_CAPABLE
779 char* get_track_filename(const int track_index)
781 if ( track_index < track_count )
782 return track_names + tracks[track_index].filename_idx;
783 return 0;
785 #endif
787 Compare two unsigned ints passed via pointers.
789 int compare_tracks (const void *a_v, const void *b_v)
791 uint32_t a = ((struct track_data *)a_v)->sort;
792 uint32_t b = ((struct track_data *)b_v)->sort;
793 return (int)(a - b);
797 Create the track index of the given slide_index.
799 void create_track_index(const int slide_index)
801 if ( slide_index == track_index )
802 return;
803 track_index = slide_index;
805 if (!rb->tagcache_search(&tcs, tag_title))
806 goto fail;
808 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
809 track_count=0;
810 int string_index = 0, track_num;
811 int disc_num;
812 size_t out = 0;
813 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
814 borrowed += out;
815 int avail = borrowed;
816 tracks = (struct track_data*)(track_names + borrowed);
817 while (rb->tagcache_get_next(&tcs))
819 int len = 0, fn_idx = 0;
821 avail -= sizeof(struct track_data);
822 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
823 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
825 if (disc_num < 0)
826 disc_num = 0;
827 retry:
828 if (track_num >= 0)
830 if (disc_num)
831 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
832 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
833 else
834 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
835 "%d: %s", track_num + 1, tcs.result);
837 else
839 track_num = 0;
840 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
841 "%s", tcs.result);
843 if (fn_idx <= 0)
844 goto fail;
845 #if PF_PLAYBACK_CAPABLE
846 int remain = avail - fn_idx;
847 if (remain >= MAX_PATH)
848 { /* retrieve filename for building the playlist */
849 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
850 track_names + string_index + fn_idx, remain);
851 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
852 /* make sure track name and file name are really split by a \0, else
853 * get_track_name might fail */
854 *(track_names + string_index + fn_idx -1) = '\0';
857 else /* request more buffer so that track and filename fit */
858 len = (avail - remain) + MAX_PATH;
859 #else
860 len = fn_idx;
861 #endif
862 if (len > avail)
864 while (len > avail)
866 if (!free_slide_prio(0))
867 goto fail;
868 out = 0;
869 buflib_buffer_out(&buf_ctx, &out);
870 avail += out;
871 borrowed += out;
872 if (track_count)
874 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
875 unsigned int bytes = track_count * sizeof(struct track_data);
876 rb->memmove(new_tracks, tracks, bytes);
877 tracks = new_tracks;
880 goto retry;
883 avail -= len;
884 tracks--;
885 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
886 tracks->name_idx = string_index;
887 tracks->seek = tcs.result_seek;
888 #if PF_PLAYBACK_CAPABLE
889 tracks->filename_idx = fn_idx + string_index;
890 #endif
891 track_count++;
892 string_index += len;
895 rb->tagcache_search_finish(&tcs);
897 /* now fix the track list order */
898 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
899 return;
900 fail:
901 track_count = 0;
902 return;
906 Determine filename of the album art for the given slide_index and
907 store the result in buf.
908 The algorithm looks for the first track of the given album uses
909 find_albumart to find the filename.
911 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
912 int buflen)
914 if ( slide_index == -1 )
916 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
919 if (!rb->tagcache_search(&tcs, tag_filename))
920 return false;
922 bool result;
923 /* find the first track of the album */
924 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
926 if ( rb->tagcache_get_next(&tcs) ) {
927 struct mp3entry id3;
928 int fd;
930 #ifdef HAVE_TC_RAMCACHE
931 if (rb->tagcache_fill_tags(&id3, tcs.result))
933 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
935 else
936 #endif
938 fd = rb->open(tcs.result, O_RDONLY);
939 rb->get_metadata(&id3, fd, tcs.result);
940 rb->close(fd);
942 if ( search_albumart_files(&id3, ":", buf, buflen) )
943 result = true;
944 else
945 result = false;
947 else {
948 /* did not find a matching track */
949 result = false;
951 rb->tagcache_search_finish(&tcs);
952 return result;
956 Draw the PictureFlow logo
958 void draw_splashscreen(void)
960 unsigned char * buf_tmp = buf;
961 size_t buf_tmp_size = buf_size;
962 struct screen* display = rb->screens[0];
963 #if FB_DATA_SZ > 1
964 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
965 #endif
966 struct bitmap logo = {
967 #if LCD_WIDTH < 200
968 .width = 100,
969 .height = 18,
970 #else
971 .width = 193,
972 .height = 34,
973 #endif
974 .data = buf_tmp
976 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
977 NULL);
978 #if LCD_DEPTH > 1
979 rb->lcd_set_background(N_BRIGHT(0));
980 rb->lcd_set_foreground(N_BRIGHT(255));
981 #else
982 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
983 #endif
984 rb->lcd_clear_display();
986 if (ret > 0)
988 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
989 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
990 #endif
991 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
992 logo.width, logo.height);
993 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
994 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
995 #endif
998 rb->lcd_update();
1003 Draw a simple progress bar
1005 void draw_progressbar(int step)
1007 int txt_w, txt_h;
1008 const int bar_height = 22;
1009 const int w = LCD_WIDTH - 20;
1010 const int x = 10;
1012 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1014 int y = (LCD_HEIGHT - txt_h)/2;
1016 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1017 y += (txt_h + 5);
1019 #if LCD_DEPTH > 1
1020 rb->lcd_set_foreground(N_BRIGHT(100));
1021 #endif
1022 rb->lcd_drawrect(x, y, w+2, bar_height);
1023 #if LCD_DEPTH > 1
1024 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1025 #endif
1027 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1028 #if LCD_DEPTH > 1
1029 rb->lcd_set_foreground(N_BRIGHT(255));
1030 #endif
1031 rb->lcd_update();
1032 rb->yield();
1036 Precomupte the album art images and store them in CACHE_PREFIX.
1038 bool create_albumart_cache(void)
1040 int ret;
1042 int i, slides = 0;
1043 struct bitmap input_bmp;
1045 char pfraw_file[MAX_PATH];
1046 char albumart_file[MAX_PATH];
1047 unsigned int format = FORMAT_NATIVE;
1048 cache_version = 0;
1049 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1050 if (resize)
1051 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1052 for (i=0; i < album_count; i++)
1054 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1056 /* delete existing cache, so it's a true rebuild */
1057 if(rb->file_exists(pfraw_file))
1058 rb->remove(pfraw_file);
1059 draw_progressbar(i);
1060 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1061 continue;
1063 input_bmp.data = buf;
1064 input_bmp.width = DISPLAY_WIDTH;
1065 input_bmp.height = DISPLAY_HEIGHT;
1066 ret = read_image_file(albumart_file, &input_bmp,
1067 buf_size, format, &format_transposed);
1068 if (ret <= 0) {
1069 rb->splash(HZ, "Could not read bmp");
1070 continue; /* skip missing/broken files */
1072 if (!save_pfraw(pfraw_file, &input_bmp))
1074 rb->splash(HZ, "Could not write bmp");
1076 slides++;
1077 if ( rb->button_get(false) == PF_MENU ) return false;
1079 if ( slides == 0 ) {
1080 /* Warn the user that we couldn't find any albumart */
1081 rb->splash(2*HZ, "No album art found");
1082 return false;
1084 return true;
1088 Thread used for loading and preparing bitmaps in the background
1090 void thread(void)
1092 long sleep_time = 5 * HZ;
1093 struct queue_event ev;
1094 while (1) {
1095 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1096 switch (ev.id) {
1097 case EV_EXIT:
1098 return;
1099 case EV_WAKEUP:
1100 /* we just woke up */
1101 break;
1103 while ( load_new_slide() ) {
1104 rb->yield();
1105 switch (ev.id) {
1106 case EV_EXIT:
1107 return;
1115 End the thread by posting the EV_EXIT event
1117 void end_pf_thread(void)
1119 if ( thread_is_running ) {
1120 rb->queue_post(&thread_q, EV_EXIT, 0);
1121 rb->thread_wait(thread_id);
1122 /* remove the thread's queue from the broadcast list */
1123 rb->queue_delete(&thread_q);
1124 thread_is_running = false;
1131 Create the thread an setup the event queue
1133 bool create_pf_thread(void)
1135 /* put the thread's queue in the bcast list */
1136 rb->queue_init(&thread_q, true);
1137 if ((thread_id = rb->create_thread(
1138 thread,
1139 thread_stack,
1140 sizeof(thread_stack),
1142 "Picture load thread"
1143 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1144 PRIORITY_REALTIME + 1))
1145 IF_COP(, CPU)
1147 ) == 0) {
1148 return false;
1150 thread_is_running = true;
1151 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1152 return true;
1156 Safe the given bitmap as filename in the pfraw format
1158 bool save_pfraw(char* filename, struct bitmap *bm)
1160 struct pfraw_header bmph;
1161 bmph.width = bm->width;
1162 bmph.height = bm->height;
1163 int fh = rb->creat( filename );
1164 if( fh < 0 ) return false;
1165 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1166 int y;
1167 for( y = 0; y < bm->height; y++ )
1169 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1170 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1172 rb->close( fh );
1173 return true;
1178 * The following functions implement the linked-list-in-array used to manage
1179 * the LRU cache of slides, and the list of free cache slots.
1182 #define seek_right_while(start, cond) \
1183 ({ \
1184 int ind_, next_ = (start); \
1185 do { \
1186 ind_ = next_; \
1187 next_ = cache[ind_].next; \
1188 } while (next_ != cache_used && (cond)); \
1189 ind_; \
1192 #define seek_left_while(start, cond) \
1193 ({ \
1194 int ind_, next_ = (start); \
1195 do { \
1196 ind_ = next_; \
1197 next_ = cache[ind_].prev; \
1198 } while (ind_ != cache_used && (cond)); \
1199 ind_; \
1203 Pop the given item from the linked list starting at *head, returning the next
1204 item, or -1 if the list is now empty.
1206 static inline int lla_pop_item (int *head, int i)
1208 int prev = cache[i].prev;
1209 int next = cache[i].next;
1210 if (i == next)
1212 *head = -1;
1213 return -1;
1215 else if (i == *head)
1216 *head = next;
1217 cache[next].prev = prev;
1218 cache[prev].next = next;
1219 return next;
1224 Pop the head item from the list starting at *head, returning the index of the
1225 item, or -1 if the list is already empty.
1227 static inline int lla_pop_head (int *head)
1229 int i = *head;
1230 if (i != -1)
1231 lla_pop_item(head, i);
1232 return i;
1236 Insert the item at index i before the one at index p.
1238 static inline void lla_insert (int i, int p)
1240 int next = p;
1241 int prev = cache[next].prev;
1242 cache[next].prev = i;
1243 cache[prev].next = i;
1244 cache[i].next = next;
1245 cache[i].prev = prev;
1250 Insert the item at index i at the end of the list starting at *head.
1252 static inline void lla_insert_tail (int *head, int i)
1254 if (*head == -1)
1256 *head = i;
1257 cache[i].next = i;
1258 cache[i].prev = i;
1259 } else
1260 lla_insert(i, *head);
1264 Insert the item at index i before the one at index p.
1266 static inline void lla_insert_after(int i, int p)
1268 p = cache[p].next;
1269 lla_insert(i, p);
1274 Insert the item at index i before the one at index p in the list starting at
1275 *head
1277 static inline void lla_insert_before(int *head, int i, int p)
1279 lla_insert(i, p);
1280 if (*head == p)
1281 *head = i;
1286 Free the used slide at index i, and its buffer, and move it to the free
1287 slides list.
1289 static inline void free_slide(int i)
1291 if (cache[i].hid != empty_slide_hid)
1292 buflib_free(&buf_ctx, cache[i].hid);
1293 cache[i].index = -1;
1294 lla_pop_item(&cache_used, i);
1295 lla_insert_tail(&cache_free, i);
1296 if (cache_used == -1)
1298 cache_right_index = -1;
1299 cache_left_index = -1;
1300 cache_center_index = -1;
1306 Free one slide ranked above the given priority. If no such slide can be found,
1307 return false.
1309 static bool free_slide_prio(int prio)
1311 if (cache_used == -1)
1312 return false;
1313 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1314 int prio_l = cache[l].index < center_index ?
1315 center_index - cache[l].index : 0;
1316 int prio_r = cache[r].index > center_index ?
1317 cache[r].index - center_index : 0;
1318 if (prio_l > prio_r)
1320 i = l;
1321 prio_max = prio_l;
1322 } else {
1323 i = r;
1324 prio_max = prio_r;
1326 if (prio_max > prio)
1328 if (i == cache_left_index)
1329 cache_left_index = cache[i].next;
1330 if (i == cache_right_index)
1331 cache_right_index = cache[i].prev;
1332 free_slide(i);
1333 return true;
1334 } else
1335 return false;
1339 Read the pfraw image given as filename and return the hid of the buffer
1341 int read_pfraw(char* filename, int prio)
1343 struct pfraw_header bmph;
1344 int fh = rb->open(filename, O_RDONLY);
1345 if( fh < 0 )
1346 return empty_slide_hid;
1347 else
1348 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1350 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1351 bmph.width * bmph.height;
1353 int hid;
1354 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1356 if (!hid) {
1357 rb->close( fh );
1358 return 0;
1361 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1363 bm->width = bmph.width;
1364 bm->height = bmph.height;
1365 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1367 int y;
1368 for( y = 0; y < bm->height; y++ )
1370 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1371 data += bm->width;
1373 rb->close( fh );
1374 return hid;
1379 Load the surface for the given slide_index into the cache at cache_index.
1381 static inline bool load_and_prepare_surface(const int slide_index,
1382 const int cache_index,
1383 const int prio)
1385 char tmp_path_name[MAX_PATH+1];
1386 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1387 slide_index);
1389 int hid = read_pfraw(tmp_path_name, prio);
1390 if (!hid)
1391 return false;
1393 cache[cache_index].hid = hid;
1395 if ( cache_index < SLIDE_CACHE_SIZE ) {
1396 cache[cache_index].index = slide_index;
1399 return true;
1404 Load the "next" slide that we can load, freeing old slides if needed, provided
1405 that they are further from center_index than the current slide
1407 bool load_new_slide(void)
1409 int i = -1;
1410 if (cache_center_index != -1)
1412 int next, prev;
1413 if (cache[cache_center_index].index != center_index)
1415 if (cache[cache_center_index].index < center_index)
1417 cache_center_index = seek_right_while(cache_center_index,
1418 cache[next_].index <= center_index);
1419 prev = cache_center_index;
1420 next = cache[cache_center_index].next;
1422 else
1424 cache_center_index = seek_left_while(cache_center_index,
1425 cache[next_].index >= center_index);
1426 next = cache_center_index;
1427 prev = cache[cache_center_index].prev;
1429 if (cache[cache_center_index].index != center_index)
1431 if (cache_free == -1)
1432 free_slide_prio(0);
1433 i = lla_pop_head(&cache_free);
1434 if (!load_and_prepare_surface(center_index, i, 0))
1435 goto fail_and_refree;
1436 if (cache[next].index == -1)
1438 if (cache[prev].index == -1)
1439 goto insert_first_slide;
1440 else
1441 next = cache[prev].next;
1443 lla_insert(i, next);
1444 if (cache[i].index < cache[cache_used].index)
1445 cache_used = i;
1446 cache_center_index = i;
1447 cache_left_index = i;
1448 cache_right_index = i;
1449 return true;
1452 if (cache[cache_left_index].index >
1453 cache[cache_center_index].index)
1454 cache_left_index = cache_center_index;
1455 if (cache[cache_right_index].index <
1456 cache[cache_center_index].index)
1457 cache_right_index = cache_center_index;
1458 cache_left_index = seek_left_while(cache_left_index,
1459 cache[ind_].index - 1 == cache[next_].index);
1460 cache_right_index = seek_right_while(cache_right_index,
1461 cache[ind_].index - 1 == cache[next_].index);
1462 int prio_l = cache[cache_center_index].index -
1463 cache[cache_left_index].index + 1;
1464 int prio_r = cache[cache_right_index].index -
1465 cache[cache_center_index].index + 1;
1466 if ((prio_l < prio_r ||
1467 cache[cache_right_index].index >= number_of_slides) &&
1468 cache[cache_left_index].index > 0)
1470 if (cache_free == -1 && !free_slide_prio(prio_l))
1471 return false;
1472 i = lla_pop_head(&cache_free);
1473 if (load_and_prepare_surface(cache[cache_left_index].index
1474 - 1, i, prio_l))
1476 lla_insert_before(&cache_used, i, cache_left_index);
1477 cache_left_index = i;
1478 return true;
1480 } else if(cache[cache_right_index].index < number_of_slides - 1)
1482 if (cache_free == -1 && !free_slide_prio(prio_r))
1483 return false;
1484 i = lla_pop_head(&cache_free);
1485 if (load_and_prepare_surface(cache[cache_right_index].index
1486 + 1, i, prio_r))
1488 lla_insert_after(i, cache_right_index);
1489 cache_right_index = i;
1490 return true;
1493 } else {
1494 i = lla_pop_head(&cache_free);
1495 if (load_and_prepare_surface(center_index, i, 0))
1497 insert_first_slide:
1498 cache[i].next = i;
1499 cache[i].prev = i;
1500 cache_center_index = i;
1501 cache_left_index = i;
1502 cache_right_index = i;
1503 cache_used = i;
1504 return true;
1507 fail_and_refree:
1508 if (i != -1)
1510 lla_insert_tail(&cache_free, i);
1512 return false;
1517 Get a slide from the buffer
1519 static inline struct dim *get_slide(const int hid)
1521 if (!hid)
1522 return NULL;
1524 struct dim *bmp;
1526 bmp = buflib_get_data(&buf_ctx, hid);
1528 return bmp;
1533 Return the requested surface
1535 static inline struct dim *surface(const int slide_index)
1537 if (slide_index < 0)
1538 return 0;
1539 if (slide_index >= number_of_slides)
1540 return 0;
1541 int i;
1542 if ((i = cache_used ) != -1)
1544 do {
1545 if (cache[i].index == slide_index)
1546 return get_slide(cache[i].hid);
1547 i = cache[i].next;
1548 } while (i != cache_used);
1550 return get_slide(empty_slide_hid);
1554 adjust slides so that they are in "steady state" position
1556 void reset_slides(void)
1558 center_slide.angle = 0;
1559 center_slide.cx = 0;
1560 center_slide.cy = 0;
1561 center_slide.distance = 0;
1562 center_slide.slide_index = center_index;
1564 int i;
1565 for (i = 0; i < num_slides; i++) {
1566 struct slide_data *si = &left_slides[i];
1567 si->angle = itilt;
1568 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1569 si->cy = offsetY;
1570 si->slide_index = center_index - 1 - i;
1571 si->distance = 0;
1574 for (i = 0; i < num_slides; i++) {
1575 struct slide_data *si = &right_slides[i];
1576 si->angle = -itilt;
1577 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1578 si->cy = offsetY;
1579 si->slide_index = center_index + 1 + i;
1580 si->distance = 0;
1586 Updates look-up table and other stuff necessary for the rendering.
1587 Call this when the viewport size or slide dimension is changed.
1589 * To calculate the offset that will provide the proper margin, we use the same
1590 * projection used to render the slides. The solution for xc, the slide center,
1591 * is:
1592 * xp * (zo + xs * sin(r))
1593 * xc = xp - xs * cos(r) + ───────────────────────
1595 * TODO: support moving the side slides toward or away from the camera
1597 void recalc_offsets(void)
1599 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1600 PFreal zo;
1601 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1602 PFREAL_ONE) * zoom / 100;
1603 PFreal cosr, sinr;
1605 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1606 cosr = fcos(-itilt);
1607 sinr = fsin(-itilt);
1608 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1609 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1610 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1611 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1612 / CAM_DIST;
1613 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1618 Fade the given color by spreading the fb_data (ushort)
1619 to an uint, multiply and compress the result back to a ushort.
1621 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1622 static inline unsigned fade_color(pix_t c, unsigned a)
1624 unsigned int result;
1625 c = swap16(c);
1626 a = (a + 2) & 0x1fc;
1627 result = ((c & 0xf81f) * a) & 0xf81f00;
1628 result |= ((c & 0x7e0) * a) & 0x7e000;
1629 result >>= 8;
1630 return swap16(result);
1632 #elif LCD_PIXELFORMAT == RGB565
1633 static inline unsigned fade_color(pix_t c, unsigned a)
1635 unsigned int result;
1636 a = (a + 2) & 0x1fc;
1637 result = ((c & 0xf81f) * a) & 0xf81f00;
1638 result |= ((c & 0x7e0) * a) & 0x7e000;
1639 result >>= 8;
1640 return result;
1642 #else
1643 static inline unsigned fade_color(pix_t c, unsigned a)
1645 unsigned val = c;
1646 return MULUQ(val, a) >> 8;
1648 #endif
1651 * Render a single slide
1652 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1653 * on the slide from its center, zo is the slide's depth offset from the plane
1654 * of the display, r is the angle at which the slide is tilted, and xp is the
1655 * point on the display corresponding to xs on the slide, the projection
1656 * formulas are:
1658 * z * (xc + xs * cos(r))
1659 * xp = ──────────────────────
1660 * z + zo + xs * sin(r)
1662 * z * (xc - xp) - xp * zo
1663 * xs = ────────────────────────
1664 * xp * sin(r) - z * cos(r)
1666 * We use the xp projection once, to find the left edge of the slide on the
1667 * display. From there, we use the xs reverse projection to find the horizontal
1668 * offset from the slide center of each column on the screen, until we reach
1669 * the right edge of the slide, or the screen. The reverse projection can be
1670 * optimized by saving the numerator and denominator of the fraction, which can
1671 * then be incremented by (z + zo) and sin(r) respectively.
1673 void render_slide(struct slide_data *slide, const int alpha)
1675 struct dim *bmp = surface(slide->slide_index);
1676 if (!bmp) {
1677 return;
1679 if (slide->angle > 255 || slide->angle < -255)
1680 return;
1681 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1683 const int sw = bmp->width;
1684 const int sh = bmp->height;
1685 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1686 const int w = LCD_WIDTH;
1688 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1690 if (alpha == 256) { /* opaque -> copy table */
1691 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1692 } else { /* precalculate faded table */
1693 int i, lalpha;
1694 for (i = 0; i < REFLECT_HEIGHT; i++) {
1695 lalpha = reflect_table[i];
1696 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1700 PFreal cosr = fcos(slide->angle);
1701 PFreal sinr = fsin(slide->angle);
1702 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1703 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1704 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1705 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1706 (CAM_DIST_R + zo + fmul(xs,sinr)));
1708 /* Since we're finding the screen position of the left edge of the slide,
1709 * we round up.
1711 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1712 >> PFREAL_SHIFT;
1713 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1714 if (xi >= w) {
1715 return;
1717 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1718 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1719 xs = fdiv(xsnum, xsden);
1721 xsnumi = -CAM_DIST_R - zo;
1722 xsdeni = sinr;
1723 int x;
1724 int dy = PFREAL_ONE;
1725 for (x = xi; x < w; x++) {
1726 int column = (xs - slide_left) / PFREAL_ONE;
1727 if (column >= sw)
1728 break;
1729 if (zo || slide->angle)
1730 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1732 const pix_t *ptr = &src[column * bmp->height];
1733 const int pixelstep = BUFFER_WIDTH;
1735 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1736 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1737 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1739 if (alpha == 256) {
1740 while (p >= plim) {
1741 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1742 p -= dy;
1743 pixel -= pixelstep;
1745 } else {
1746 while (p >= plim) {
1747 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1748 p -= dy;
1749 pixel -= pixelstep;
1752 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1753 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1754 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1755 p + (LCD_HEIGHT/2) * dy);
1756 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1758 if (alpha == 256) {
1759 while (p < plim) {
1760 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1761 p += dy;
1762 pixel += pixelstep;
1764 } else {
1765 while (p < plim) {
1766 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1767 p += dy;
1768 pixel += pixelstep;
1771 while (p < plim2) {
1772 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1773 int lalpha = reftab[ty];
1774 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1775 p += dy;
1776 pixel += pixelstep;
1779 if (zo || slide->angle)
1781 xsnum += xsnumi;
1782 xsden += xsdeni;
1783 xs = fdiv(xsnum, xsden);
1784 } else
1785 xs += PFREAL_ONE;
1788 /* let the music play... */
1789 rb->yield();
1790 return;
1795 Jump the the given slide_index
1797 static inline void set_current_slide(const int slide_index)
1799 int old_center_index = center_index;
1800 step = 0;
1801 center_index = fbound(slide_index, 0, number_of_slides - 1);
1802 if (old_center_index != center_index)
1803 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1804 target = center_index;
1805 slide_frame = slide_index << 16;
1806 reset_slides();
1810 Start the animation for changing slides
1812 void start_animation(void)
1814 step = (target < center_slide.slide_index) ? -1 : 1;
1815 pf_state = pf_scrolling;
1819 Go to the previous slide
1821 void show_previous_slide(void)
1823 if (step == 0) {
1824 if (center_index > 0) {
1825 target = center_index - 1;
1826 start_animation();
1828 } else if ( step > 0 ) {
1829 target = center_index;
1830 start_animation();
1831 } else {
1832 target = fmax(0, center_index - 2);
1838 Go to the next slide
1840 void show_next_slide(void)
1842 if (step == 0) {
1843 if (center_index < number_of_slides - 1) {
1844 target = center_index + 1;
1845 start_animation();
1847 } else if ( step < 0 ) {
1848 target = center_index;
1849 start_animation();
1850 } else {
1851 target = fmin(center_index + 2, number_of_slides - 1);
1857 Render the slides. Updates only the offscreen buffer.
1859 void render_all_slides(void)
1861 MYLCD(set_background)(G_BRIGHT(0));
1862 /* TODO: Optimizes this by e.g. invalidating rects */
1863 MYLCD(clear_display)();
1865 int nleft = num_slides;
1866 int nright = num_slides;
1868 int index;
1869 if (step == 0) {
1870 /* no animation, boring plain rendering */
1871 for (index = nleft - 2; index >= 0; index--) {
1872 int alpha = (index < nleft - 2) ? 256 : 128;
1873 alpha -= extra_fade;
1874 if (alpha > 0 )
1875 render_slide(&left_slides[index], alpha);
1877 for (index = nright - 2; index >= 0; index--) {
1878 int alpha = (index < nright - 2) ? 256 : 128;
1879 alpha -= extra_fade;
1880 if (alpha > 0 )
1881 render_slide(&right_slides[index], alpha);
1883 } else {
1884 /* the first and last slide must fade in/fade out */
1885 for (index = nleft - 1; index >= 0; index--) {
1886 int alpha = 256;
1887 if (index == nleft - 1)
1888 alpha = (step > 0) ? 0 : 128 - fade / 2;
1889 if (index == nleft - 2)
1890 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1891 if (index == nleft - 3)
1892 alpha = (step > 0) ? 256 - fade / 2 : 256;
1893 render_slide(&left_slides[index], alpha);
1895 for (index = nright - 1; index >= 0; index--) {
1896 int alpha = (index < nright - 2) ? 256 : 128;
1897 if (index == nright - 1)
1898 alpha = (step > 0) ? fade / 2 : 0;
1899 if (index == nright - 2)
1900 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1901 if (index == nright - 3)
1902 alpha = (step > 0) ? 256 : 128 + fade / 2;
1903 render_slide(&right_slides[index], alpha);
1906 render_slide(&center_slide, 256);
1911 Updates the animation effect. Call this periodically from a timer.
1913 void update_scroll_animation(void)
1915 if (step == 0)
1916 return;
1918 int speed = 16384;
1919 int i;
1921 /* deaccelerate when approaching the target */
1922 if (true) {
1923 const int max = 2 * 65536;
1925 int fi = slide_frame;
1926 fi -= (target << 16);
1927 if (fi < 0)
1928 fi = -fi;
1929 fi = fmin(fi, max);
1931 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1932 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1935 slide_frame += speed * step;
1937 int index = slide_frame >> 16;
1938 int pos = slide_frame & 0xffff;
1939 int neg = 65536 - pos;
1940 int tick = (step < 0) ? neg : pos;
1941 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1943 /* the leftmost and rightmost slide must fade away */
1944 fade = pos / 256;
1946 if (step < 0)
1947 index++;
1948 if (center_index != index) {
1949 center_index = index;
1950 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1951 slide_frame = index << 16;
1952 center_slide.slide_index = center_index;
1953 for (i = 0; i < num_slides; i++)
1954 left_slides[i].slide_index = center_index - 1 - i;
1955 for (i = 0; i < num_slides; i++)
1956 right_slides[i].slide_index = center_index + 1 + i;
1959 center_slide.angle = (step * tick * itilt) >> 16;
1960 center_slide.cx = -step * fmul(offsetX, ftick);
1961 center_slide.cy = fmul(offsetY, ftick);
1963 if (center_index == target) {
1964 reset_slides();
1965 pf_state = pf_idle;
1966 step = 0;
1967 fade = 256;
1968 return;
1971 for (i = 0; i < num_slides; i++) {
1972 struct slide_data *si = &left_slides[i];
1973 si->angle = itilt;
1974 si->cx =
1975 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1976 * slide_spacing * ftick);
1977 si->cy = offsetY;
1980 for (i = 0; i < num_slides; i++) {
1981 struct slide_data *si = &right_slides[i];
1982 si->angle = -itilt;
1983 si->cx =
1984 offsetX + slide_spacing * i * PFREAL_ONE - step
1985 * slide_spacing * ftick;
1986 si->cy = offsetY;
1989 if (step > 0) {
1990 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1991 right_slides[0].angle = -(neg * itilt) >> 16;
1992 right_slides[0].cx = fmul(offsetX, ftick);
1993 right_slides[0].cy = fmul(offsetY, ftick);
1994 } else {
1995 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1996 left_slides[0].angle = (pos * itilt) >> 16;
1997 left_slides[0].cx = -fmul(offsetX, ftick);
1998 left_slides[0].cy = fmul(offsetY, ftick);
2001 /* must change direction ? */
2002 if (target < index)
2003 if (step > 0)
2004 step = -1;
2005 if (target > index)
2006 if (step < 0)
2007 step = 1;
2012 Cleanup the plugin
2014 static void cleanup(UNUSED_ATTR void *parameter)
2016 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2017 rb->cpu_boost(false);
2018 #endif
2019 end_pf_thread();
2020 /* Turn on backlight timeout (revert to settings) */
2021 backlight_use_settings(); /* backlight control in lib/helper.c */
2023 #ifdef USEGSLIB
2024 grey_release();
2025 #endif
2029 Create the "?" slide, that is shown while loading
2030 or when no cover was found.
2032 int create_empty_slide(bool force)
2034 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2035 struct bitmap input_bmp;
2036 int ret;
2037 input_bmp.width = DISPLAY_WIDTH;
2038 input_bmp.height = DISPLAY_HEIGHT;
2039 #if LCD_DEPTH > 1
2040 input_bmp.format = FORMAT_NATIVE;
2041 #endif
2042 input_bmp.data = (char*)buf;
2043 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2044 buf_size,
2045 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2046 &format_transposed);
2047 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2048 return false;
2051 return true;
2055 Shows the settings menu
2057 int settings_menu(void)
2059 int selection = 0;
2060 bool old_val;
2062 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2063 "Spacing", "Centre margin", "Number of slides", "Zoom",
2064 "Show album title", "Resize Covers", "Rebuild cache");
2066 static const struct opt_items album_name_options[] = {
2067 { "Hide album title", -1 },
2068 { "Show at the bottom", -1 },
2069 { "Show at the top", -1 }
2072 do {
2073 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2074 switch(selection) {
2075 case 0:
2076 rb->set_bool("Show FPS", &show_fps);
2077 reset_track_list();
2078 break;
2080 case 1:
2081 rb->set_int("Spacing between slides", "", 1,
2082 &slide_spacing,
2083 NULL, 1, 0, 100, NULL );
2084 recalc_offsets();
2085 reset_slides();
2086 break;
2088 case 2:
2089 rb->set_int("Centre margin", "", 1,
2090 &center_margin,
2091 NULL, 1, 0, 80, NULL );
2092 recalc_offsets();
2093 reset_slides();
2094 break;
2096 case 3:
2097 rb->set_int("Number of slides", "", 1, &num_slides,
2098 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2099 recalc_offsets();
2100 reset_slides();
2101 break;
2103 case 4:
2104 rb->set_int("Zoom", "", 1, &zoom,
2105 NULL, 1, 10, 300, NULL );
2106 recalc_offsets();
2107 reset_slides();
2108 break;
2109 case 5:
2110 rb->set_option("Show album title", &show_album_name,
2111 INT, album_name_options, 3, NULL);
2112 reset_track_list();
2113 recalc_offsets();
2114 reset_slides();
2115 break;
2116 case 6:
2117 old_val = resize;
2118 rb->set_bool("Resize Covers", &resize);
2119 if (old_val == resize) /* changed? */
2120 break;
2121 /* fallthrough if changed, since cache needs to be rebuilt */
2122 case 7:
2123 cache_version = 0;
2124 rb->remove(EMPTY_SLIDE);
2125 rb->splash(HZ, "Cache will be rebuilt on next restart");
2126 break;
2128 case MENU_ATTACHED_USB:
2129 return PLUGIN_USB_CONNECTED;
2131 } while ( selection >= 0 );
2132 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2133 return 0;
2137 Show the main menu
2139 enum {
2140 PF_GOTO_WPS,
2141 #if PF_PLAYBACK_CAPABLE
2142 PF_MENU_PLAYBACK_CONTROL,
2143 #endif
2144 PF_MENU_SETTINGS,
2145 PF_MENU_RETURN,
2146 PF_MENU_QUIT,
2149 int main_menu(void)
2151 int selection = 0;
2152 int result;
2154 #if LCD_DEPTH > 1
2155 rb->lcd_set_foreground(N_BRIGHT(255));
2156 #endif
2158 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2159 "Go to WPS",
2160 #if PF_PLAYBACK_CAPABLE
2161 "Playback Control",
2162 #endif
2163 "Settings", "Return", "Quit");
2164 while (1) {
2165 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2166 case PF_GOTO_WPS: /* WPS */
2167 return -2;
2168 #if PF_PLAYBACK_CAPABLE
2169 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2170 playback_control(NULL);
2171 break;
2172 #endif
2173 case PF_MENU_SETTINGS:
2174 result = settings_menu();
2175 if ( result != 0 ) return result;
2176 break;
2177 case PF_MENU_RETURN:
2178 return 0;
2179 case PF_MENU_QUIT:
2180 return -1;
2182 case MENU_ATTACHED_USB:
2183 return PLUGIN_USB_CONNECTED;
2185 default:
2186 return 0;
2192 Animation step for zooming into the current cover
2194 void update_cover_in_animation(void)
2196 cover_animation_keyframe++;
2197 if( cover_animation_keyframe < 20 ) {
2198 center_slide.distance-=5;
2199 center_slide.angle+=1;
2200 extra_fade += 13;
2202 else if( cover_animation_keyframe < 35 ) {
2203 center_slide.angle+=16;
2205 else {
2206 cover_animation_keyframe = 0;
2207 pf_state = pf_show_tracks;
2212 Animation step for zooming out the current cover
2214 void update_cover_out_animation(void)
2216 cover_animation_keyframe++;
2217 if( cover_animation_keyframe <= 15 ) {
2218 center_slide.angle-=16;
2220 else if( cover_animation_keyframe < 35 ) {
2221 center_slide.distance+=5;
2222 center_slide.angle-=1;
2223 extra_fade -= 13;
2225 else {
2226 cover_animation_keyframe = 0;
2227 pf_state = pf_idle;
2232 Draw a blue gradient at y with height h
2234 static inline void draw_gradient(int y, int h)
2236 static int r, inc, c;
2237 inc = (100 << 8) / h;
2238 c = 0;
2239 selected_track_pulse = (selected_track_pulse+1) % 10;
2240 int c2 = selected_track_pulse - 5;
2241 for (r=0; r<h; r++) {
2242 #ifdef HAVE_LCD_COLOR
2243 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2244 c2+250-(c >> 8)));
2245 #else
2246 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2247 #endif
2248 MYLCD(hline)(0, LCD_WIDTH, r+y);
2249 if ( r > h/2 )
2250 c-=inc;
2251 else
2252 c+=inc;
2257 static void track_list_yh(int char_height)
2259 switch (show_album_name)
2261 case album_name_hide:
2262 track_list_y = (show_fps ? char_height : 0);
2263 track_list_h = LCD_HEIGHT - track_list_y;
2264 break;
2265 case album_name_bottom:
2266 track_list_y = (show_fps ? char_height : 0);
2267 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2268 break;
2269 default: /* case album_name_top */
2270 track_list_y = char_height * 2;
2271 track_list_h = LCD_HEIGHT - track_list_y -
2272 (show_fps ? char_height : 0);
2273 break;
2278 Reset the track list after a album change
2280 void reset_track_list(void)
2282 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2283 track_list_yh(albumtxt_h);
2284 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2285 start_index_track_list = 0;
2286 track_scroll_index = 0;
2287 track_scroll_dir = 1;
2288 selected_track = 0;
2290 /* let the tracklist start more centered
2291 * if the screen isn't filled with tracks */
2292 if (track_count*albumtxt_h < track_list_h)
2294 track_list_h = track_count * albumtxt_h;
2295 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2300 Display the list of tracks
2302 void show_track_list(void)
2304 MYLCD(clear_display)();
2305 if ( center_slide.slide_index != track_index ) {
2306 create_track_index(center_slide.slide_index);
2307 reset_track_list();
2309 static int titletxt_w, titletxt_x, color, titletxt_h;
2310 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2312 int titletxt_y = track_list_y;
2313 int track_i;
2314 track_i = start_index_track_list;
2315 for (;track_i < track_list_visible_entries+start_index_track_list;
2316 track_i++)
2318 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2319 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2320 if ( track_i == selected_track ) {
2321 draw_gradient(titletxt_y, titletxt_h);
2322 MYLCD(set_foreground)(G_BRIGHT(255));
2323 if (titletxt_w > LCD_WIDTH ) {
2324 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2325 track_scroll_dir = 1;
2326 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2327 track_scroll_index += track_scroll_dir*2;
2328 titletxt_x = track_scroll_index;
2330 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2332 else {
2333 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2334 MYLCD(set_foreground)(G_BRIGHT(color));
2335 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2337 titletxt_y += titletxt_h;
2341 void select_next_track(void)
2343 if ( selected_track < track_count - 1 ) {
2344 selected_track++;
2345 track_scroll_index = 0;
2346 track_scroll_dir = 1;
2347 if (selected_track==(track_list_visible_entries+start_index_track_list))
2348 start_index_track_list++;
2352 void select_prev_track(void)
2354 if (selected_track > 0 ) {
2355 if (selected_track==start_index_track_list) start_index_track_list--;
2356 track_scroll_index = 0;
2357 track_scroll_dir = 1;
2358 selected_track--;
2362 #if PF_PLAYBACK_CAPABLE
2364 * Puts the current tracklist into a newly created playlist and starts playling
2366 void start_playback(void)
2368 static int old_playlist = -1, old_shuffle = 0;
2369 int count = 0;
2370 int position = selected_track;
2371 int shuffle = rb->global_settings->playlist_shuffle;
2372 /* reuse existing playlist if possible
2373 * regenerate if shuffle is on or changed, since playlist index and
2374 * selected track are "out of sync" */
2375 if (!shuffle && center_slide.slide_index == old_playlist
2376 && (old_shuffle == shuffle))
2378 goto play;
2380 /* First, replace the current playlist with a new one */
2381 else if (rb->playlist_remove_all_tracks(NULL) == 0
2382 && rb->playlist_create(NULL, NULL) == 0)
2384 do {
2385 rb->yield();
2386 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2387 PLAYLIST_INSERT_LAST, false, true) < 0)
2388 break;
2389 } while(++count < track_count);
2390 rb->playlist_sync(NULL);
2392 else
2393 return;
2395 if (rb->global_settings->playlist_shuffle)
2396 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2397 play:
2398 /* TODO: can we adjust selected_track if !play_selected ?
2399 * if shuffle, we can't predict the playing track easily, and for either
2400 * case the track list doesn't get auto scrolled*/
2401 rb->playlist_start(position, 0);
2402 old_playlist = center_slide.slide_index;
2403 old_shuffle = shuffle;
2405 #endif
2408 Draw the current album name
2410 void draw_album_text(void)
2412 if (0 == show_album_name)
2413 return;
2415 int albumtxt_w, albumtxt_h;
2416 int albumtxt_y = 0;
2418 char *albumtxt;
2419 int c;
2420 /* Draw album text */
2421 if ( pf_state == pf_scrolling ) {
2422 c = ((slide_frame & 0xffff )/ 255);
2423 if (step < 0) c = 255-c;
2424 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2425 albumtxt = get_album_name(center_index+step);
2426 c = (c-128)*2;
2428 else {
2429 albumtxt = get_album_name(center_index);
2430 c = (128-c)*2;
2433 else {
2434 c= 255;
2435 albumtxt = get_album_name(center_index);
2438 MYLCD(set_foreground)(G_BRIGHT(c));
2439 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2440 if (center_index != prev_center_index) {
2441 albumtxt_x = 0;
2442 albumtxt_dir = -1;
2443 prev_center_index = center_index;
2446 if (show_album_name == album_name_top)
2447 albumtxt_y = albumtxt_h / 2;
2448 else
2449 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2451 if (albumtxt_w > LCD_WIDTH ) {
2452 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2453 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2454 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2455 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2456 albumtxt_x += albumtxt_dir;
2459 else {
2460 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2467 Display an error message and wait for input.
2469 void error_wait(const char *message)
2471 rb->splashf(0, "%s. Press any button to continue.", message);
2472 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2473 rb->yield();
2474 rb->sleep(2 * HZ);
2478 Main function that also contain the main plasma
2479 algorithm.
2481 int main(void)
2483 int ret;
2485 rb->lcd_setfont(FONT_UI);
2486 draw_splashscreen();
2488 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2489 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2490 error_wait("Could not create directory " CACHE_PREFIX);
2491 return PLUGIN_ERROR;
2495 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2497 init_reflect_table();
2499 ALIGN_BUFFER(buf, buf_size, 4);
2500 ret = create_album_index();
2501 if (ret == ERROR_BUFFER_FULL) {
2502 error_wait("Not enough memory for album names");
2503 return PLUGIN_ERROR;
2504 } else if (ret == ERROR_NO_ALBUMS) {
2505 error_wait("No albums found. Please enable database");
2506 return PLUGIN_ERROR;
2509 ALIGN_BUFFER(buf, buf_size, 4);
2510 number_of_slides = album_count;
2511 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2512 error_wait("Could not create album art cache");
2513 return PLUGIN_ERROR;
2516 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2517 error_wait("Could not load the empty slide");
2518 return PLUGIN_ERROR;
2520 cache_version = CACHE_VERSION;
2521 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2524 #ifdef USEGSLIB
2525 long grey_buf_used;
2526 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2527 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2529 error_wait("Greylib init failed!");
2530 return PLUGIN_ERROR;
2532 grey_setfont(FONT_UI);
2533 buf_size -= grey_buf_used;
2534 buf = (void*)(grey_buf_used + (char*)buf);
2535 #endif
2536 buflib_init(&buf_ctx, (void *)buf, buf_size);
2538 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2540 error_wait("Unable to load empty slide image");
2541 return PLUGIN_ERROR;
2544 if (!create_pf_thread()) {
2545 error_wait("Cannot create thread!");
2546 return PLUGIN_ERROR;
2549 int i;
2551 /* initialize */
2552 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2553 cache[i].hid = 0;
2554 cache[i].index = 0;
2555 cache[i].next = i + 1;
2556 cache[i].prev = i - 1;
2558 cache[0].prev = i - 1;
2559 cache[i - 1].next = 0;
2560 cache_free = 0;
2561 buffer = LCD_BUF;
2563 pf_state = pf_idle;
2565 track_index = -1;
2566 extra_fade = 0;
2567 slide_frame = 0;
2568 step = 0;
2569 target = 0;
2570 fade = 256;
2572 recalc_offsets();
2573 reset_slides();
2575 char fpstxt[10];
2576 int button;
2578 int frames = 0;
2579 long last_update = *rb->current_tick;
2580 long current_update;
2581 long update_interval = 100;
2582 int fps = 0;
2583 int fpstxt_y;
2585 bool instant_update;
2586 #ifdef USEGSLIB
2587 grey_show(true);
2588 grey_set_drawmode(DRMODE_FG);
2589 #endif
2590 rb->lcd_set_drawmode(DRMODE_FG);
2591 while (true) {
2592 current_update = *rb->current_tick;
2593 frames++;
2595 /* Initial rendering */
2596 instant_update = false;
2598 /* Handle states */
2599 switch ( pf_state ) {
2600 case pf_scrolling:
2601 update_scroll_animation();
2602 render_all_slides();
2603 instant_update = true;
2604 break;
2605 case pf_cover_in:
2606 update_cover_in_animation();
2607 render_all_slides();
2608 instant_update = true;
2609 break;
2610 case pf_cover_out:
2611 update_cover_out_animation();
2612 render_all_slides();
2613 instant_update = true;
2614 break;
2615 case pf_show_tracks:
2616 show_track_list();
2617 break;
2618 case pf_idle:
2619 render_all_slides();
2620 break;
2623 /* Calculate FPS */
2624 if (current_update - last_update > update_interval) {
2625 fps = frames * HZ / (current_update - last_update);
2626 last_update = current_update;
2627 frames = 0;
2629 /* Draw FPS */
2630 if (show_fps)
2632 #ifdef USEGSLIB
2633 MYLCD(set_foreground)(G_BRIGHT(255));
2634 #else
2635 MYLCD(set_foreground)(G_PIX(255,0,0));
2636 #endif
2637 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2638 if (show_album_name == album_name_top)
2639 fpstxt_y = LCD_HEIGHT -
2640 rb->screens[SCREEN_MAIN]->getcharheight();
2641 else
2642 fpstxt_y = 0;
2643 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2645 draw_album_text();
2648 /* Copy offscreen buffer to LCD and give time to other threads */
2649 MYLCD(update)();
2650 rb->yield();
2652 /*/ Handle buttons */
2653 button = rb->get_custom_action(CONTEXT_PLUGIN
2654 #ifndef USE_CORE_PREVNEXT
2655 |(pf_state == pf_show_tracks ? 1 : 0)
2656 #endif
2657 ,instant_update ? 0 : HZ/16,
2658 get_context_map);
2660 switch (button) {
2661 case PF_QUIT:
2662 return PLUGIN_OK;
2663 case PF_WPS:
2664 return PLUGIN_GOTO_WPS;
2665 case PF_BACK:
2666 if ( pf_state == pf_show_tracks )
2668 buflib_buffer_in(&buf_ctx, borrowed);
2669 borrowed = 0;
2670 track_index = -1;
2671 pf_state = pf_cover_out;
2673 if (pf_state == pf_idle || pf_state == pf_scrolling)
2674 return PLUGIN_OK;
2675 break;
2676 case PF_MENU:
2677 #ifdef USEGSLIB
2678 grey_show(false);
2679 #endif
2680 ret = main_menu();
2681 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2682 if ( ret == -1 ) return PLUGIN_OK;
2683 if ( ret != 0 ) return ret;
2684 #ifdef USEGSLIB
2685 grey_show(true);
2686 #endif
2687 MYLCD(set_drawmode)(DRMODE_FG);
2688 break;
2690 case PF_NEXT:
2691 case PF_NEXT_REPEAT:
2692 if ( pf_state == pf_show_tracks )
2693 select_next_track();
2694 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2695 show_next_slide();
2696 break;
2698 case PF_PREV:
2699 case PF_PREV_REPEAT:
2700 if ( pf_state == pf_show_tracks )
2701 select_prev_track();
2702 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2703 show_previous_slide();
2704 break;
2706 case PF_SELECT:
2707 if ( pf_state == pf_idle ) {
2708 pf_state = pf_cover_in;
2710 else if ( pf_state == pf_show_tracks ) {
2711 #if PF_PLAYBACK_CAPABLE
2712 start_playback();
2713 #endif
2715 break;
2716 default:
2717 if (rb->default_event_handler_ex(button, cleanup, NULL)
2718 == SYS_USB_CONNECTED)
2719 return PLUGIN_USB_CONNECTED;
2720 break;
2725 /*************************** Plugin entry point ****************************/
2727 enum plugin_status plugin_start(UNUSED_ATTR const void *parameter)
2729 int ret;
2730 #if LCD_DEPTH > 1
2731 rb->lcd_set_backdrop(NULL);
2732 #endif
2733 /* Turn off backlight timeout */
2734 backlight_force_on(); /* backlight control in lib/helper.c */
2735 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2736 rb->cpu_boost(true);
2737 #endif
2738 #if PF_PLAYBACK_CAPABLE
2739 buf = rb->plugin_get_buffer(&buf_size);
2740 #else
2741 buf = rb->plugin_get_audio_buffer(&buf_size);
2742 #ifndef SIMULATOR
2743 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2745 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2746 buf_size = MIN(buf_size, tmp_size);
2748 #endif
2749 #endif
2750 ret = main();
2751 if ( ret == PLUGIN_OK ) {
2752 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2753 CONFIG_VERSION))
2755 rb->splash(HZ, "Error writing config.");
2756 ret = PLUGIN_ERROR;
2760 cleanup(NULL);
2761 return ret;