Suggested by Thomas Martitz, use tagcache_fill_tags to speed up PictureFlow album...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob2baea2a73069c95306daa7194c35ee03d26473dc
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 ***********************************/
40 #define PF_PREV ACTION_STD_PREV
41 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
42 #define PF_NEXT ACTION_STD_NEXT
43 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
44 #define PF_SELECT ACTION_STD_OK
45 #define PF_CONTEXT ACTION_STD_CONTEXT
46 #define PF_BACK ACTION_STD_CANCEL
47 #define PF_MENU ACTION_STD_MENU
48 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
50 const struct button_mapping pf_context_album_scroll[] =
52 #ifdef HAVE_TOUCHSCREEN
53 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
54 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
55 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
56 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
57 #endif
58 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
59 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
60 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
61 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
62 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
63 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
64 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
65 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
66 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
67 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
68 CONFIG_KEYPAD == MEIZU_M6SL_PAD
69 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
70 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
71 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
72 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
73 #elif CONFIG_KEYPAD == ONDIO_PAD
74 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
75 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
76 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
77 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
79 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
80 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
81 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
82 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
83 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
84 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
85 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
86 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
87 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
88 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
89 #endif
90 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
93 const struct button_mapping pf_context_buttons[] =
95 #ifdef HAVE_TOUCHSCREEN
96 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
97 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
98 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
99 #endif
100 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
101 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
102 #elif CONFIG_KEYPAD == SANSA_C100_PAD
103 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
104 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
105 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
106 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
107 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
108 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
109 CONFIG_KEYPAD == SANSA_FUZE_PAD
110 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
111 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
113 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
114 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
115 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
116 #if CONFIG_KEYPAD == COWOND2_PAD
117 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
118 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
119 #endif
120 #elif CONFIG_KEYPAD == SANSA_E200_PAD
121 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
122 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
123 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
124 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
125 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
126 || (CONFIG_KEYPAD == IPOD_4G_PAD)
127 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
128 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
129 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
130 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
131 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
132 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
133 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
134 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
135 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
136 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
137 #endif
138 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
139 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
140 #else
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
142 #endif
144 const struct button_mapping *pf_contexts[] =
146 pf_context_album_scroll,
147 pf_context_buttons
150 #if LCD_DEPTH < 8
151 #if LCD_DEPTH > 1
152 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
153 #else /* LCD_DEPTH <= 1 */
154 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
155 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
156 #define PICTUREFLOW_DRMODE DRMODE_SOLID
157 #else
158 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
159 #endif
160 #endif /* LCD_DEPTH <= 1 */
161 #define USEGSLIB
162 GREY_INFO_STRUCT
163 #define LCD_BUF _grey_info.buffer
164 #define MYLCD(fn) grey_ ## fn
165 #define G_PIX(r,g,b) \
166 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
167 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
168 #define G_BRIGHT(y) (y)
169 #define BUFFER_WIDTH _grey_info.width
170 #define BUFFER_HEIGHT _grey_info.height
171 typedef unsigned char pix_t;
172 #else /* LCD_DEPTH >= 8 */
173 #define LCD_BUF rb->lcd_framebuffer
174 #define MYLCD(fn) rb->lcd_ ## fn
175 #define G_PIX LCD_RGBPACK
176 #define N_PIX LCD_RGBPACK
177 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
178 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
179 #define BUFFER_WIDTH LCD_WIDTH
180 #define BUFFER_HEIGHT LCD_HEIGHT
181 typedef fb_data pix_t;
182 #endif /* LCD_DEPTH >= 8 */
184 /* for fixed-point arithmetic, we need minimum 32-bit long
185 long long (64-bit) might be useful for multiplication and division */
186 #define PFreal long
187 #define PFREAL_SHIFT 10
188 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
189 #define PFREAL_ONE (1 << PFREAL_SHIFT)
190 #define PFREAL_HALF (PFREAL_ONE >> 1)
193 #define IANGLE_MAX 1024
194 #define IANGLE_MASK 1023
196 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
197 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
198 #define DISPLAY_HEIGHT REFLECT_TOP
199 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
200 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
201 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
202 (REFLECT_HEIGHT * 5))
203 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
204 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
205 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
206 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
207 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
209 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
211 #define MAX_SLIDES_COUNT 10
213 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
214 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
216 #define EV_EXIT 9999
217 #define EV_WAKEUP 1337
219 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
220 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
221 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
223 /* Error return values */
224 #define ERROR_NO_ALBUMS -1
225 #define ERROR_BUFFER_FULL -2
227 /* current version for cover cache */
228 #define CACHE_VERSION 3
229 #define CONFIG_VERSION 1
230 #define CONFIG_FILE "pictureflow.cfg"
232 /** structs we use */
234 struct slide_data {
235 int slide_index;
236 int angle;
237 PFreal cx;
238 PFreal cy;
239 PFreal distance;
242 struct slide_cache {
243 int index; /* index of the cached slide */
244 int hid; /* handle ID of the cached slide */
245 short next; /* "next" slide, with LRU last */
246 short prev; /* "previous" slide */
249 struct album_data {
250 int name_idx;
251 long seek;
254 struct track_data {
255 uint32_t sort;
256 int name_idx;
257 long seek;
260 struct rect {
261 int left;
262 int right;
263 int top;
264 int bottom;
267 struct load_slide_event_data {
268 int slide_index;
269 int cache_index;
273 struct pfraw_header {
274 int32_t width; /* bmap width in pixels */
275 int32_t height; /* bmap height in pixels */
278 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
279 album_name_top };
280 static char* show_album_name_conf[] =
282 "hide",
283 "bottom",
284 "top"
287 #define MAX_SPACING 40
288 #define MAX_MARGIN 80
290 /* config values and their defaults */
291 static int slide_spacing = DISPLAY_WIDTH / 4;
292 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
293 static int num_slides = 4;
294 static int zoom = 100;
295 static bool show_fps = false;
296 static bool resize = true;
297 static int cache_version = 0;
298 static int show_album_name = (LCD_HEIGHT > 100)
299 ? album_name_top : album_name_bottom;
301 static struct configdata config[] =
303 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
304 NULL },
305 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
306 NULL },
307 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
308 NULL },
309 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
310 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
311 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
312 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
313 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
314 show_album_name_conf }
317 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
319 /** below we allocate the memory we want to use **/
321 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
322 static uint8_t reflect_table[REFLECT_HEIGHT];
323 static struct slide_data center_slide;
324 static struct slide_data left_slides[MAX_SLIDES_COUNT];
325 static struct slide_data right_slides[MAX_SLIDES_COUNT];
326 static int slide_frame;
327 static int step;
328 static int target;
329 static int fade;
330 static int center_index = 0; /* index of the slide that is in the center */
331 static int itilt;
332 static PFreal offsetX;
333 static PFreal offsetY;
334 static int number_of_slides;
336 static struct slide_cache cache[SLIDE_CACHE_SIZE];
337 static int cache_free;
338 static int cache_used = -1;
339 static int cache_left_index = -1;
340 static int cache_right_index = -1;
341 static int cache_center_index = -1;
343 /* use long for aligning */
344 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
345 /* queue (as array) for scheduling load_surface */
347 static int empty_slide_hid;
349 unsigned int thread_id;
350 struct event_queue thread_q;
352 static struct tagcache_search tcs;
354 static struct buflib_context buf_ctx;
356 static struct album_data *album;
357 static char *album_names;
358 static int album_count;
360 static struct track_data *tracks;
361 static char *track_names;
362 static size_t borrowed = 0;
363 static int track_count;
364 static int track_index;
365 static int selected_track;
366 static int selected_track_pulse;
367 void reset_track_list(void);
369 void * buf;
370 size_t buf_size;
372 static bool thread_is_running;
374 static int cover_animation_keyframe;
375 static int extra_fade;
377 static int albumtxt_x = 0;
378 static int albumtxt_dir = -1;
379 static int prev_center_index = -1;
381 static int start_index_track_list = 0;
382 static int track_list_visible_entries = 0;
383 static int track_list_y;
384 static int track_list_h;
385 static int track_scroll_index = 0;
386 static int track_scroll_dir = 1;
389 Proposals for transitions:
391 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
392 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
394 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
396 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
398 TODO:
399 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
400 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
402 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
404 enum pf_states {
405 pf_idle = 0,
406 pf_scrolling,
407 pf_cover_in,
408 pf_show_tracks,
409 pf_cover_out
412 static int pf_state;
414 /** code */
415 static bool free_slide_prio(int prio);
416 static inline unsigned fade_color(pix_t c, unsigned a);
417 bool save_pfraw(char* filename, struct bitmap *bm);
418 bool load_new_slide(void);
419 int load_surface(int);
421 static inline PFreal fmul(PFreal a, PFreal b)
423 return (a*b) >> PFREAL_SHIFT;
427 * This version preshifts each operand, which is useful when we know how many
428 * of the least significant bits will be empty, or are worried about overflow
429 * in a particular calculation
431 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
433 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
436 /* ARMv5+ has a clz instruction equivalent to our function.
438 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
439 static inline int clz(uint32_t v)
441 return __builtin_clz(v);
444 /* Otherwise, use our clz, which can be inlined */
445 #elif defined(CPU_COLDFIRE)
446 /* This clz is based on the log2(n) implementation at
447 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
448 * A clz benchmark plugin showed this to be about 14% faster on coldfire
449 * than the LUT-based version.
451 static inline int clz(uint32_t v)
453 int r = 32;
454 if (v >= 0x10000)
456 v >>= 16;
457 r -= 16;
459 if (v & 0xff00)
461 v >>= 8;
462 r -= 8;
464 if (v & 0xf0)
466 v >>= 4;
467 r -= 4;
469 if (v & 0xc)
471 v >>= 2;
472 r -= 2;
474 if (v & 2)
476 v >>= 1;
477 r -= 1;
479 r -= v;
480 return r;
482 #else
483 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
484 0, 0, 0, 0, 0, 0, 0, 0 };
485 /* This clz is based on the log2(n) implementation at
486 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
487 * It is not any faster than the one above, but trades 16B in the lookup table
488 * for a savings of 12B per each inlined call.
490 static inline int clz(uint32_t v)
492 int r = 28;
493 if (v >= 0x10000)
495 v >>= 16;
496 r -= 16;
498 if (v & 0xff00)
500 v >>= 8;
501 r -= 8;
503 if (v & 0xf0)
505 v >>= 4;
506 r -= 4;
508 return r + clz_lut[v];
510 #endif
512 /* Return the maximum possible left shift for a signed int32, without
513 * overflow
515 static inline int allowed_shift(int32_t val)
517 uint32_t uval = val ^ (val >> 31);
518 return clz(uval) - 1;
521 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
522 * num and den before dividing.
524 static inline PFreal fdiv(PFreal num, PFreal den)
526 int shift = allowed_shift(num);
527 shift = MIN(PFREAL_SHIFT, shift);
528 num <<= shift;
529 den >>= PFREAL_SHIFT - shift;
530 return num / den;
533 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
534 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
535 #define fabs(a) (a < 0 ? -a : a)
536 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
538 #if CONFIG_CPU == SH7034
539 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
540 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
541 #else
542 #define MULUQ(a, b) ((a) * (b))
543 #endif
546 #if 0
547 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
548 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
550 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
551 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
553 static inline PFreal fmul(PFreal a, PFreal b)
555 return (a*b) >> PFREAL_SHIFT;
558 static inline PFreal fdiv(PFreal n, PFreal m)
560 return (n<<(PFREAL_SHIFT))/m;
562 #endif
564 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
565 static const short sin_tab[] = {
566 0, 100, 200, 297, 392, 483, 569, 650,
567 724, 792, 851, 903, 946, 980, 1004, 1019,
568 1024, 1019, 1004, 980, 946, 903, 851, 792,
569 724, 650, 569, 483, 392, 297, 200, 100,
570 0, -100, -200, -297, -392, -483, -569, -650,
571 -724, -792, -851, -903, -946, -980, -1004, -1019,
572 -1024, -1019, -1004, -980, -946, -903, -851, -792,
573 -724, -650, -569, -483, -392, -297, -200, -100,
577 static inline PFreal fsin(int iangle)
579 iangle &= IANGLE_MASK;
581 int i = (iangle >> 4);
582 PFreal p = sin_tab[i];
583 PFreal q = sin_tab[(i+1)];
584 PFreal g = (q - p);
585 return p + g * (iangle-i*16)/16;
588 static inline PFreal fcos(int iangle)
590 return fsin(iangle + (IANGLE_MAX >> 2));
593 static inline unsigned scale_val(unsigned val, unsigned bits)
595 val = val * ((1 << bits) - 1);
596 return ((val >> 8) + val + 128) >> 8;
599 static void output_row_8_transposed(uint32_t row, void * row_in,
600 struct scaler_context *ctx)
602 pix_t *dest = (pix_t*)ctx->bm->data + row;
603 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
604 #ifdef USEGSLIB
605 uint8_t *qp = (uint8_t*)row_in;
606 for (; dest < end; dest += ctx->bm->height)
607 *dest = *qp++;
608 #else
609 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
610 unsigned r, g, b;
611 for (; dest < end; dest += ctx->bm->height)
613 r = scale_val(qp->red, 5);
614 g = scale_val(qp->green, 6);
615 b = scale_val((qp++)->blue, 5);
616 *dest = LCD_RGBPACK_LCD(r,g,b);
618 #endif
621 static void output_row_32_transposed(uint32_t row, void * row_in,
622 struct scaler_context *ctx)
624 pix_t *dest = (pix_t*)ctx->bm->data + row;
625 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
626 #ifdef USEGSLIB
627 uint32_t *qp = (uint32_t*)row_in;
628 for (; dest < end; dest += ctx->bm->height)
629 *dest = SC_OUT(*qp++, ctx);
630 #else
631 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
632 int r, g, b;
633 for (; dest < end; dest += ctx->bm->height)
635 r = scale_val(SC_OUT(qp->r, ctx), 5);
636 g = scale_val(SC_OUT(qp->g, ctx), 6);
637 b = scale_val(SC_OUT(qp->b, ctx), 5);
638 qp++;
639 *dest = LCD_RGBPACK_LCD(r,g,b);
641 #endif
644 #ifdef HAVE_LCD_COLOR
645 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
646 struct scaler_context *ctx)
648 pix_t *dest = (pix_t*)ctx->bm->data + row;
649 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
650 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
651 for (; dest < end; dest += ctx->bm->height)
653 unsigned r, g, b, y, u, v;
654 y = SC_OUT(qp->b, ctx);
655 u = SC_OUT(qp->g, ctx);
656 v = SC_OUT(qp->r, ctx);
657 qp++;
658 yuv_to_rgb(y, u, v, &r, &g, &b);
659 r = scale_val(r, 5);
660 g = scale_val(g, 6);
661 b = scale_val(b, 5);
662 *dest = LCD_RGBPACK_LCD(r, g, b);
665 #endif
667 static unsigned int get_size(struct bitmap *bm)
669 return bm->width * bm->height * sizeof(pix_t);
672 const struct custom_format format_transposed = {
673 .output_row_8 = output_row_8_transposed,
674 #ifdef HAVE_LCD_COLOR
675 .output_row_32 = {
676 output_row_32_transposed,
677 output_row_32_transposed_fromyuv
679 #else
680 .output_row_32 = output_row_32_transposed,
681 #endif
682 .get_size = get_size
685 static const struct button_mapping* get_context_map(int context)
687 return pf_contexts[context & ~CONTEXT_CUSTOM];
690 /* Create the lookup table with the scaling values for the reflections */
691 void init_reflect_table(void)
693 int i;
694 for (i = 0; i < REFLECT_HEIGHT; i++)
695 reflect_table[i] =
696 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
697 (5 * REFLECT_HEIGHT);
701 Create an index of all albums from the database.
702 Also store the album names so we can access them later.
704 int create_album_index(void)
706 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
707 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
708 album_count = 0;
709 rb->tagcache_search(&tcs, tag_album);
710 unsigned int l, old_l = 0;
711 album_names = buf;
712 album[0].name_idx = 0;
713 while (rb->tagcache_get_next(&tcs))
715 buf_size -= sizeof(struct album_data);
716 l = tcs.result_len;
717 if ( album_count > 0 )
718 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
720 if ( l > buf_size )
721 /* not enough memory */
722 return ERROR_BUFFER_FULL;
724 rb->strcpy(buf, tcs.result);
725 buf_size -= l;
726 buf = l + (char *)buf;
727 DEBUGF("%lX: %s\n", tcs.idxfd[tag_album] ? rb->lseek(tcs.idxfd[tag_album], 0, SEEK_CUR) : -1, tcs.result);
728 album[-album_count].seek = tcs.result_seek;
729 old_l = l;
730 album_count++;
732 rb->tagcache_search_finish(&tcs);
733 ALIGN_BUFFER(buf, buf_size, 4);
734 int i;
735 struct album_data* tmp_album = (struct album_data*)buf;
736 for (i = album_count - 1; i >= 0; i--)
737 tmp_album[i] = album[-i];
738 album = tmp_album;
739 buf = album + album_count;
740 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
744 Return a pointer to the album name of the given slide_index
746 char* get_album_name(const int slide_index)
748 return album_names + album[slide_index].name_idx;
752 Return a pointer to the track name of the active album
753 create_track_index has to be called first.
755 char* get_track_name(const int track_index)
757 if ( track_index < track_count )
758 return track_names + tracks[track_index].name_idx;
759 return 0;
763 Compare two unsigned ints passed via pointers.
765 int compare_tracks (const void *a_v, const void *b_v)
767 uint32_t a = ((struct track_data *)a_v)->sort;
768 uint32_t b = ((struct track_data *)b_v)->sort;
769 return (int)(a - b);
773 Create the track index of the given slide_index.
775 void create_track_index(const int slide_index)
777 if ( slide_index == track_index )
778 return;
779 track_index = slide_index;
781 if (!rb->tagcache_search(&tcs, tag_title))
782 goto fail;
784 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
785 track_count=0;
786 int string_index = 0, track_num;
787 int disc_num;
788 size_t out = 0;
789 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
790 borrowed += out;
791 int avail = borrowed;
792 tracks = (struct track_data*)(track_names + borrowed);
793 while (rb->tagcache_get_next(&tcs))
795 avail -= sizeof(struct track_data);
796 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
797 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
798 int len = 0;
799 if (disc_num < 0)
800 disc_num = 0;
801 retry:
802 if (track_num >= 0)
804 if (disc_num)
805 len = 1 + rb->snprintf(track_names + string_index , avail,
806 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
807 else
808 len = 1 + rb->snprintf(track_names + string_index , avail,
809 "%d: %s", track_num + 1, tcs.result);
811 else
813 track_num = 0;
814 len = tcs.result_len;
815 rb->strncpy(track_names + string_index, tcs.result, avail);
817 if (len > avail)
819 while (len > avail)
821 if (!free_slide_prio(0))
822 goto fail;
823 out = 0;
824 buflib_buffer_out(&buf_ctx, &out);
825 avail += out;
826 borrowed += out;
827 if (track_count)
829 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
830 unsigned int bytes = track_count * sizeof(struct track_data);
831 rb->memmove(new_tracks, tracks, bytes);
832 tracks = new_tracks;
835 goto retry;
838 avail -= len;
839 tracks--;
840 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
841 tracks->name_idx = string_index;
842 tracks->seek = tcs.result_seek;
843 track_count++;
844 string_index += len;
847 rb->tagcache_search_finish(&tcs);
849 /* now fix the track list order */
850 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
851 return;
852 fail:
853 track_count = 0;
854 return;
858 Determine filename of the album art for the given slide_index and
859 store the result in buf.
860 The algorithm looks for the first track of the given album uses
861 find_albumart to find the filename.
863 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
864 int buflen)
866 if ( slide_index == -1 )
868 rb->strncpy( buf, EMPTY_SLIDE, buflen );
871 if (!rb->tagcache_search(&tcs, tag_filename))
872 return false;
874 bool result;
875 /* find the first track of the album */
876 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
878 if ( rb->tagcache_get_next(&tcs) ) {
879 struct mp3entry id3;
880 int fd;
882 #ifdef HAVE_TC_RAMCACHE
883 if (rb->tagcache_fill_tags(&id3, tcs.result))
885 rb->strncpy(id3.path, tcs.result, sizeof(id3.path));
886 id3.path[sizeof(id3.path) - 1] = 0;
888 else
889 #endif
891 fd = rb->open(tcs.result, O_RDONLY);
892 rb->get_metadata(&id3, fd, tcs.result);
893 rb->close(fd);
895 if ( search_albumart_files(&id3, "", buf, buflen) )
896 result = true;
897 else
898 result = false;
900 else {
901 /* did not find a matching track */
902 result = false;
904 rb->tagcache_search_finish(&tcs);
905 return result;
909 Draw the PictureFlow logo
911 void draw_splashscreen(void)
913 unsigned char * buf_tmp = buf;
914 size_t buf_tmp_size = buf_size;
915 struct screen* display = rb->screens[0];
916 #if FB_DATA_SZ > 1
917 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
918 #endif
919 struct bitmap logo = {
920 #if LCD_WIDTH < 200
921 .width = 100,
922 .height = 18,
923 #else
924 .width = 193,
925 .height = 34,
926 #endif
927 .data = buf_tmp
929 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
930 NULL);
931 #if LCD_DEPTH > 1
932 rb->lcd_set_background(N_BRIGHT(0));
933 rb->lcd_set_foreground(N_BRIGHT(255));
934 #else
935 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
936 #endif
937 rb->lcd_clear_display();
939 if (ret > 0)
941 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
942 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
943 #endif
944 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
945 logo.width, logo.height);
946 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
947 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
948 #endif
951 rb->lcd_update();
956 Draw a simple progress bar
958 void draw_progressbar(int step)
960 int txt_w, txt_h;
961 const int bar_height = 22;
962 const int w = LCD_WIDTH - 20;
963 const int x = 10;
965 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
967 int y = (LCD_HEIGHT - txt_h)/2;
969 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
970 y += (txt_h + 5);
972 #if LCD_DEPTH > 1
973 rb->lcd_set_foreground(N_BRIGHT(100));
974 #endif
975 rb->lcd_drawrect(x, y, w+2, bar_height);
976 #if LCD_DEPTH > 1
977 rb->lcd_set_foreground(N_PIX(165, 231, 82));
978 #endif
980 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
981 #if LCD_DEPTH > 1
982 rb->lcd_set_foreground(N_BRIGHT(255));
983 #endif
984 rb->lcd_update();
985 rb->yield();
989 Precomupte the album art images and store them in CACHE_PREFIX.
991 bool create_albumart_cache(void)
993 int ret;
995 int i, slides = 0;
996 struct bitmap input_bmp;
998 char pfraw_file[MAX_PATH];
999 char albumart_file[MAX_PATH];
1000 unsigned int format = FORMAT_NATIVE;
1001 cache_version = 0;
1002 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1003 if (resize)
1004 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1005 for (i=0; i < album_count; i++)
1007 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1009 /* delete existing cache, so it's a true rebuild */
1010 if(rb->file_exists(pfraw_file))
1011 rb->remove(pfraw_file);
1012 draw_progressbar(i);
1013 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1014 continue;
1016 input_bmp.data = buf;
1017 input_bmp.width = DISPLAY_WIDTH;
1018 input_bmp.height = DISPLAY_HEIGHT;
1019 ret = read_image_file(albumart_file, &input_bmp,
1020 buf_size, format, &format_transposed);
1021 if (ret <= 0) {
1022 rb->splash(HZ, "Could not read bmp");
1023 continue; /* skip missing/broken files */
1025 if (!save_pfraw(pfraw_file, &input_bmp))
1027 rb->splash(HZ, "Could not write bmp");
1029 slides++;
1030 if ( rb->button_get(false) == PF_MENU ) return false;
1032 if ( slides == 0 ) {
1033 /* Warn the user that we couldn't find any albumart */
1034 rb->splash(2*HZ, "No album art found");
1035 return false;
1037 return true;
1041 Thread used for loading and preparing bitmaps in the background
1043 void thread(void)
1045 long sleep_time = 5 * HZ;
1046 struct queue_event ev;
1047 while (1) {
1048 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1049 switch (ev.id) {
1050 case EV_EXIT:
1051 return;
1052 case EV_WAKEUP:
1053 /* we just woke up */
1054 break;
1056 while ( load_new_slide() ) {
1057 rb->yield();
1058 switch (ev.id) {
1059 case EV_EXIT:
1060 return;
1068 End the thread by posting the EV_EXIT event
1070 void end_pf_thread(void)
1072 if ( thread_is_running ) {
1073 rb->queue_post(&thread_q, EV_EXIT, 0);
1074 rb->thread_wait(thread_id);
1075 /* remove the thread's queue from the broadcast list */
1076 rb->queue_delete(&thread_q);
1077 thread_is_running = false;
1084 Create the thread an setup the event queue
1086 bool create_pf_thread(void)
1088 /* put the thread's queue in the bcast list */
1089 rb->queue_init(&thread_q, true);
1090 if ((thread_id = rb->create_thread(
1091 thread,
1092 thread_stack,
1093 sizeof(thread_stack),
1095 "Picture load thread"
1096 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1097 PRIORITY_REALTIME + 1))
1098 IF_COP(, CPU)
1100 ) == 0) {
1101 return false;
1103 thread_is_running = true;
1104 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1105 return true;
1109 Safe the given bitmap as filename in the pfraw format
1111 bool save_pfraw(char* filename, struct bitmap *bm)
1113 struct pfraw_header bmph;
1114 bmph.width = bm->width;
1115 bmph.height = bm->height;
1116 int fh = rb->creat( filename );
1117 if( fh < 0 ) return false;
1118 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1119 int y;
1120 for( y = 0; y < bm->height; y++ )
1122 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1123 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1125 rb->close( fh );
1126 return true;
1131 * The following functions implement the linked-list-in-array used to manage
1132 * the LRU cache of slides, and the list of free cache slots.
1135 #define seek_right_while(start, cond) \
1136 ({ \
1137 int ind_, next_ = (start); \
1138 do { \
1139 ind_ = next_; \
1140 next_ = cache[ind_].next; \
1141 } while (next_ != cache_used && (cond)); \
1142 ind_; \
1145 #define seek_left_while(start, cond) \
1146 ({ \
1147 int ind_, next_ = (start); \
1148 do { \
1149 ind_ = next_; \
1150 next_ = cache[ind_].prev; \
1151 } while (ind_ != cache_used && (cond)); \
1152 ind_; \
1156 Pop the given item from the linked list starting at *head, returning the next
1157 item, or -1 if the list is now empty.
1159 static inline int lla_pop_item (int *head, int i)
1161 int prev = cache[i].prev;
1162 int next = cache[i].next;
1163 if (i == next)
1165 *head = -1;
1166 return -1;
1168 else if (i == *head)
1169 *head = next;
1170 cache[next].prev = prev;
1171 cache[prev].next = next;
1172 return next;
1177 Pop the head item from the list starting at *head, returning the index of the
1178 item, or -1 if the list is already empty.
1180 static inline int lla_pop_head (int *head)
1182 int i = *head;
1183 if (i != -1)
1184 lla_pop_item(head, i);
1185 return i;
1189 Insert the item at index i before the one at index p.
1191 static inline void lla_insert (int i, int p)
1193 int next = p;
1194 int prev = cache[next].prev;
1195 cache[next].prev = i;
1196 cache[prev].next = i;
1197 cache[i].next = next;
1198 cache[i].prev = prev;
1203 Insert the item at index i at the end of the list starting at *head.
1205 static inline void lla_insert_tail (int *head, int i)
1207 if (*head == -1)
1209 *head = i;
1210 cache[i].next = i;
1211 cache[i].prev = i;
1212 } else
1213 lla_insert(i, *head);
1217 Insert the item at index i before the one at index p.
1219 static inline void lla_insert_after(int i, int p)
1221 p = cache[p].next;
1222 lla_insert(i, p);
1227 Insert the item at index i before the one at index p in the list starting at
1228 *head
1230 static inline void lla_insert_before(int *head, int i, int p)
1232 lla_insert(i, p);
1233 if (*head == p)
1234 *head = i;
1239 Free the used slide at index i, and its buffer, and move it to the free
1240 slides list.
1242 static inline void free_slide(int i)
1244 if (cache[i].hid != empty_slide_hid)
1245 buflib_free(&buf_ctx, cache[i].hid);
1246 cache[i].index = -1;
1247 lla_pop_item(&cache_used, i);
1248 lla_insert_tail(&cache_free, i);
1249 if (cache_used == -1)
1251 cache_right_index = -1;
1252 cache_left_index = -1;
1253 cache_center_index = -1;
1259 Free one slide ranked above the given priority. If no such slide can be found,
1260 return false.
1262 static bool free_slide_prio(int prio)
1264 if (cache_used == -1)
1265 return false;
1266 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1267 int prio_l = cache[l].index < center_index ?
1268 center_index - cache[l].index : 0;
1269 int prio_r = cache[r].index > center_index ?
1270 cache[r].index - center_index : 0;
1271 if (prio_l > prio_r)
1273 i = l;
1274 prio_max = prio_l;
1275 } else {
1276 i = r;
1277 prio_max = prio_r;
1279 if (prio_max > prio)
1281 if (i == cache_left_index)
1282 cache_left_index = cache[i].next;
1283 if (i == cache_right_index)
1284 cache_right_index = cache[i].prev;
1285 free_slide(i);
1286 return true;
1287 } else
1288 return false;
1292 Read the pfraw image given as filename and return the hid of the buffer
1294 int read_pfraw(char* filename, int prio)
1296 struct pfraw_header bmph;
1297 int fh = rb->open(filename, O_RDONLY);
1298 if( fh < 0 )
1299 return empty_slide_hid;
1300 else
1301 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1303 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1304 bmph.width * bmph.height;
1306 int hid;
1307 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1309 if (!hid) {
1310 rb->close( fh );
1311 return 0;
1314 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1316 bm->width = bmph.width;
1317 bm->height = bmph.height;
1318 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1320 int y;
1321 for( y = 0; y < bm->height; y++ )
1323 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1324 data += bm->width;
1326 rb->close( fh );
1327 return hid;
1332 Load the surface for the given slide_index into the cache at cache_index.
1334 static inline bool load_and_prepare_surface(const int slide_index,
1335 const int cache_index,
1336 const int prio)
1338 char tmp_path_name[MAX_PATH+1];
1339 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1340 slide_index);
1342 int hid = read_pfraw(tmp_path_name, prio);
1343 if (!hid)
1344 return false;
1346 cache[cache_index].hid = hid;
1348 if ( cache_index < SLIDE_CACHE_SIZE ) {
1349 cache[cache_index].index = slide_index;
1352 return true;
1357 Load the "next" slide that we can load, freeing old slides if needed, provided
1358 that they are further from center_index than the current slide
1360 bool load_new_slide(void)
1362 int i = -1;
1363 if (cache_center_index != -1)
1365 int next, prev;
1366 if (cache[cache_center_index].index != center_index)
1368 if (cache[cache_center_index].index < center_index)
1370 cache_center_index = seek_right_while(cache_center_index,
1371 cache[next_].index <= center_index);
1372 prev = cache_center_index;
1373 next = cache[cache_center_index].next;
1375 else
1377 cache_center_index = seek_left_while(cache_center_index,
1378 cache[next_].index >= center_index);
1379 next = cache_center_index;
1380 prev = cache[cache_center_index].prev;
1382 if (cache[cache_center_index].index != center_index)
1384 if (cache_free == -1)
1385 free_slide_prio(0);
1386 i = lla_pop_head(&cache_free);
1387 if (!load_and_prepare_surface(center_index, i, 0))
1388 goto fail_and_refree;
1389 if (cache[next].index == -1)
1391 if (cache[prev].index == -1)
1392 goto insert_first_slide;
1393 else
1394 next = cache[prev].next;
1396 lla_insert(i, next);
1397 if (cache[i].index < cache[cache_used].index)
1398 cache_used = i;
1399 cache_center_index = i;
1400 cache_left_index = i;
1401 cache_right_index = i;
1402 return true;
1405 if (cache[cache_left_index].index >
1406 cache[cache_center_index].index)
1407 cache_left_index = cache_center_index;
1408 if (cache[cache_right_index].index <
1409 cache[cache_center_index].index)
1410 cache_right_index = cache_center_index;
1411 cache_left_index = seek_left_while(cache_left_index,
1412 cache[ind_].index - 1 == cache[next_].index);
1413 cache_right_index = seek_right_while(cache_right_index,
1414 cache[ind_].index - 1 == cache[next_].index);
1415 int prio_l = cache[cache_center_index].index -
1416 cache[cache_left_index].index + 1;
1417 int prio_r = cache[cache_right_index].index -
1418 cache[cache_center_index].index + 1;
1419 if ((prio_l < prio_r ||
1420 cache[cache_right_index].index >= number_of_slides) &&
1421 cache[cache_left_index].index > 0)
1423 if (cache_free == -1 && !free_slide_prio(prio_l))
1424 return false;
1425 i = lla_pop_head(&cache_free);
1426 if (load_and_prepare_surface(cache[cache_left_index].index
1427 - 1, i, prio_l))
1429 lla_insert_before(&cache_used, i, cache_left_index);
1430 cache_left_index = i;
1431 return true;
1433 } else if(cache[cache_right_index].index < number_of_slides - 1)
1435 if (cache_free == -1 && !free_slide_prio(prio_r))
1436 return false;
1437 i = lla_pop_head(&cache_free);
1438 if (load_and_prepare_surface(cache[cache_right_index].index
1439 + 1, i, prio_r))
1441 lla_insert_after(i, cache_right_index);
1442 cache_right_index = i;
1443 return true;
1446 } else {
1447 i = lla_pop_head(&cache_free);
1448 if (load_and_prepare_surface(center_index, i, 0))
1450 insert_first_slide:
1451 cache[i].next = i;
1452 cache[i].prev = i;
1453 cache_center_index = i;
1454 cache_left_index = i;
1455 cache_right_index = i;
1456 cache_used = i;
1457 return true;
1460 fail_and_refree:
1461 if (i != -1)
1463 lla_insert_tail(&cache_free, i);
1465 return false;
1470 Get a slide from the buffer
1472 static inline struct dim *get_slide(const int hid)
1474 if (!hid)
1475 return NULL;
1477 struct dim *bmp;
1479 bmp = buflib_get_data(&buf_ctx, hid);
1481 return bmp;
1486 Return the requested surface
1488 static inline struct dim *surface(const int slide_index)
1490 if (slide_index < 0)
1491 return 0;
1492 if (slide_index >= number_of_slides)
1493 return 0;
1494 int i;
1495 if ((i = cache_used ) != -1)
1497 do {
1498 if (cache[i].index == slide_index)
1499 return get_slide(cache[i].hid);
1500 i = cache[i].next;
1501 } while (i != cache_used);
1503 return get_slide(empty_slide_hid);
1507 adjust slides so that they are in "steady state" position
1509 void reset_slides(void)
1511 center_slide.angle = 0;
1512 center_slide.cx = 0;
1513 center_slide.cy = 0;
1514 center_slide.distance = 0;
1515 center_slide.slide_index = center_index;
1517 int i;
1518 for (i = 0; i < num_slides; i++) {
1519 struct slide_data *si = &left_slides[i];
1520 si->angle = itilt;
1521 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1522 si->cy = offsetY;
1523 si->slide_index = center_index - 1 - i;
1524 si->distance = 0;
1527 for (i = 0; i < num_slides; i++) {
1528 struct slide_data *si = &right_slides[i];
1529 si->angle = -itilt;
1530 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1531 si->cy = offsetY;
1532 si->slide_index = center_index + 1 + i;
1533 si->distance = 0;
1539 Updates look-up table and other stuff necessary for the rendering.
1540 Call this when the viewport size or slide dimension is changed.
1542 * To calculate the offset that will provide the proper margin, we use the same
1543 * projection used to render the slides. The solution for xc, the slide center,
1544 * is:
1545 * xp * (zo + xs * sin(r))
1546 * xc = xp - xs * cos(r) + ───────────────────────
1548 * TODO: support moving the side slides toward or away from the camera
1550 void recalc_offsets(void)
1552 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1553 PFreal zo;
1554 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1555 PFREAL_ONE) * zoom / 100;
1556 PFreal cosr, sinr;
1558 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1559 cosr = fcos(-itilt);
1560 sinr = fsin(-itilt);
1561 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1562 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1563 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1564 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1565 / CAM_DIST;
1566 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1571 Fade the given color by spreading the fb_data (ushort)
1572 to an uint, multiply and compress the result back to a ushort.
1574 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1575 static inline unsigned fade_color(pix_t c, unsigned a)
1577 unsigned int result;
1578 c = swap16(c);
1579 a = (a + 2) & 0x1fc;
1580 result = ((c & 0xf81f) * a) & 0xf81f00;
1581 result |= ((c & 0x7e0) * a) & 0x7e000;
1582 result >>= 8;
1583 return swap16(result);
1585 #elif LCD_PIXELFORMAT == RGB565
1586 static inline unsigned fade_color(pix_t c, unsigned a)
1588 unsigned int result;
1589 a = (a + 2) & 0x1fc;
1590 result = ((c & 0xf81f) * a) & 0xf81f00;
1591 result |= ((c & 0x7e0) * a) & 0x7e000;
1592 result >>= 8;
1593 return result;
1595 #else
1596 static inline unsigned fade_color(pix_t c, unsigned a)
1598 unsigned val = c;
1599 return MULUQ(val, a) >> 8;
1601 #endif
1604 * Render a single slide
1605 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1606 * on the slide from its center, zo is the slide's depth offset from the plane
1607 * of the display, r is the angle at which the slide is tilted, and xp is the
1608 * point on the display corresponding to xs on the slide, the projection
1609 * formulas are:
1611 * z * (xc + xs * cos(r))
1612 * xp = ──────────────────────
1613 * z + zo + xs * sin(r)
1615 * z * (xc - xp) - xp * zo
1616 * xs = ────────────────────────
1617 * xp * sin(r) - z * cos(r)
1619 * We use the xp projection once, to find the left edge of the slide on the
1620 * display. From there, we use the xs reverse projection to find the horizontal
1621 * offset from the slide center of each column on the screen, until we reach
1622 * the right edge of the slide, or the screen. The reverse projection can be
1623 * optimized by saving the numerator and denominator of the fraction, which can
1624 * then be incremented by (z + zo) and sin(r) respectively.
1626 void render_slide(struct slide_data *slide, const int alpha)
1628 struct dim *bmp = surface(slide->slide_index);
1629 if (!bmp) {
1630 return;
1632 if (slide->angle > 255 || slide->angle < -255)
1633 return;
1634 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1636 const int sw = bmp->width;
1637 const int sh = bmp->height;
1638 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1639 const int w = LCD_WIDTH;
1641 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1643 if (alpha == 256) { /* opaque -> copy table */
1644 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1645 } else { /* precalculate faded table */
1646 int i, lalpha;
1647 for (i = 0; i < REFLECT_HEIGHT; i++) {
1648 lalpha = reflect_table[i];
1649 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1653 PFreal cosr = fcos(slide->angle);
1654 PFreal sinr = fsin(slide->angle);
1655 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1656 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1657 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1658 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1659 (CAM_DIST_R + zo + fmul(xs,sinr)));
1661 /* Since we're finding the screen position of the left edge of the slide,
1662 * we round up.
1664 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1665 >> PFREAL_SHIFT;
1666 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1667 if (xi >= w) {
1668 return;
1670 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1671 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1672 xs = fdiv(xsnum, xsden);
1674 xsnumi = -CAM_DIST_R - zo;
1675 xsdeni = sinr;
1676 int x;
1677 int dy = PFREAL_ONE;
1678 for (x = xi; x < w; x++) {
1679 int column = (xs - slide_left) / PFREAL_ONE;
1680 if (column >= sw)
1681 break;
1682 if (zo || slide->angle)
1683 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1685 const pix_t *ptr = &src[column * bmp->height];
1686 const int pixelstep = BUFFER_WIDTH;
1688 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1689 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1690 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1692 if (alpha == 256) {
1693 while (p >= plim) {
1694 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1695 p -= dy;
1696 pixel -= pixelstep;
1698 } else {
1699 while (p >= plim) {
1700 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1701 p -= dy;
1702 pixel -= pixelstep;
1705 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1706 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1707 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1708 p + (LCD_HEIGHT/2) * dy);
1709 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1711 if (alpha == 256) {
1712 while (p < plim) {
1713 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1714 p += dy;
1715 pixel += pixelstep;
1717 } else {
1718 while (p < plim) {
1719 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1720 p += dy;
1721 pixel += pixelstep;
1724 while (p < plim2) {
1725 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1726 int lalpha = reftab[ty];
1727 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1728 p += dy;
1729 pixel += pixelstep;
1732 if (zo || slide->angle)
1734 xsnum += xsnumi;
1735 xsden += xsdeni;
1736 xs = fdiv(xsnum, xsden);
1737 } else
1738 xs += PFREAL_ONE;
1741 /* let the music play... */
1742 rb->yield();
1743 return;
1748 Jump the the given slide_index
1750 static inline void set_current_slide(const int slide_index)
1752 int old_center_index = center_index;
1753 step = 0;
1754 center_index = fbound(slide_index, 0, number_of_slides - 1);
1755 if (old_center_index != center_index)
1756 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1757 target = center_index;
1758 slide_frame = slide_index << 16;
1759 reset_slides();
1763 Start the animation for changing slides
1765 void start_animation(void)
1767 step = (target < center_slide.slide_index) ? -1 : 1;
1768 pf_state = pf_scrolling;
1772 Go to the previous slide
1774 void show_previous_slide(void)
1776 if (step == 0) {
1777 if (center_index > 0) {
1778 target = center_index - 1;
1779 start_animation();
1781 } else if ( step > 0 ) {
1782 target = center_index;
1783 start_animation();
1784 } else {
1785 target = fmax(0, center_index - 2);
1791 Go to the next slide
1793 void show_next_slide(void)
1795 if (step == 0) {
1796 if (center_index < number_of_slides - 1) {
1797 target = center_index + 1;
1798 start_animation();
1800 } else if ( step < 0 ) {
1801 target = center_index;
1802 start_animation();
1803 } else {
1804 target = fmin(center_index + 2, number_of_slides - 1);
1810 Render the slides. Updates only the offscreen buffer.
1812 void render_all_slides(void)
1814 MYLCD(set_background)(G_BRIGHT(0));
1815 /* TODO: Optimizes this by e.g. invalidating rects */
1816 MYLCD(clear_display)();
1818 int nleft = num_slides;
1819 int nright = num_slides;
1821 int index;
1822 if (step == 0) {
1823 /* no animation, boring plain rendering */
1824 for (index = nleft - 2; index >= 0; index--) {
1825 int alpha = (index < nleft - 2) ? 256 : 128;
1826 alpha -= extra_fade;
1827 if (alpha > 0 )
1828 render_slide(&left_slides[index], alpha);
1830 for (index = nright - 2; index >= 0; index--) {
1831 int alpha = (index < nright - 2) ? 256 : 128;
1832 alpha -= extra_fade;
1833 if (alpha > 0 )
1834 render_slide(&right_slides[index], alpha);
1836 } else {
1837 /* the first and last slide must fade in/fade out */
1838 for (index = nleft - 1; index >= 0; index--) {
1839 int alpha = 256;
1840 if (index == nleft - 1)
1841 alpha = (step > 0) ? 0 : 128 - fade / 2;
1842 if (index == nleft - 2)
1843 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1844 if (index == nleft - 3)
1845 alpha = (step > 0) ? 256 - fade / 2 : 256;
1846 render_slide(&left_slides[index], alpha);
1848 for (index = nright - 1; index >= 0; index--) {
1849 int alpha = (index < nright - 2) ? 256 : 128;
1850 if (index == nright - 1)
1851 alpha = (step > 0) ? fade / 2 : 0;
1852 if (index == nright - 2)
1853 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1854 if (index == nright - 3)
1855 alpha = (step > 0) ? 256 : 128 + fade / 2;
1856 render_slide(&right_slides[index], alpha);
1859 render_slide(&center_slide, 256);
1864 Updates the animation effect. Call this periodically from a timer.
1866 void update_scroll_animation(void)
1868 if (step == 0)
1869 return;
1871 int speed = 16384;
1872 int i;
1874 /* deaccelerate when approaching the target */
1875 if (true) {
1876 const int max = 2 * 65536;
1878 int fi = slide_frame;
1879 fi -= (target << 16);
1880 if (fi < 0)
1881 fi = -fi;
1882 fi = fmin(fi, max);
1884 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1885 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1888 slide_frame += speed * step;
1890 int index = slide_frame >> 16;
1891 int pos = slide_frame & 0xffff;
1892 int neg = 65536 - pos;
1893 int tick = (step < 0) ? neg : pos;
1894 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1896 /* the leftmost and rightmost slide must fade away */
1897 fade = pos / 256;
1899 if (step < 0)
1900 index++;
1901 if (center_index != index) {
1902 center_index = index;
1903 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1904 slide_frame = index << 16;
1905 center_slide.slide_index = center_index;
1906 for (i = 0; i < num_slides; i++)
1907 left_slides[i].slide_index = center_index - 1 - i;
1908 for (i = 0; i < num_slides; i++)
1909 right_slides[i].slide_index = center_index + 1 + i;
1912 center_slide.angle = (step * tick * itilt) >> 16;
1913 center_slide.cx = -step * fmul(offsetX, ftick);
1914 center_slide.cy = fmul(offsetY, ftick);
1916 if (center_index == target) {
1917 reset_slides();
1918 pf_state = pf_idle;
1919 step = 0;
1920 fade = 256;
1921 return;
1924 for (i = 0; i < num_slides; i++) {
1925 struct slide_data *si = &left_slides[i];
1926 si->angle = itilt;
1927 si->cx =
1928 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1929 * slide_spacing * ftick);
1930 si->cy = offsetY;
1933 for (i = 0; i < num_slides; i++) {
1934 struct slide_data *si = &right_slides[i];
1935 si->angle = -itilt;
1936 si->cx =
1937 offsetX + slide_spacing * i * PFREAL_ONE - step
1938 * slide_spacing * ftick;
1939 si->cy = offsetY;
1942 if (step > 0) {
1943 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1944 right_slides[0].angle = -(neg * itilt) >> 16;
1945 right_slides[0].cx = fmul(offsetX, ftick);
1946 right_slides[0].cy = fmul(offsetY, ftick);
1947 } else {
1948 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1949 left_slides[0].angle = (pos * itilt) >> 16;
1950 left_slides[0].cx = -fmul(offsetX, ftick);
1951 left_slides[0].cy = fmul(offsetY, ftick);
1954 /* must change direction ? */
1955 if (target < index)
1956 if (step > 0)
1957 step = -1;
1958 if (target > index)
1959 if (step < 0)
1960 step = 1;
1965 Cleanup the plugin
1967 void cleanup(void *parameter)
1969 (void) parameter;
1970 /* Turn on backlight timeout (revert to settings) */
1971 backlight_use_settings(); /* backlight control in lib/helper.c */
1973 #ifdef USEGSLIB
1974 grey_release();
1975 #endif
1979 Create the "?" slide, that is shown while loading
1980 or when no cover was found.
1982 int create_empty_slide(bool force)
1984 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1985 struct bitmap input_bmp;
1986 int ret;
1987 input_bmp.width = DISPLAY_WIDTH;
1988 input_bmp.height = DISPLAY_HEIGHT;
1989 #if LCD_DEPTH > 1
1990 input_bmp.format = FORMAT_NATIVE;
1991 #endif
1992 input_bmp.data = (char*)buf;
1993 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1994 buf_size,
1995 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1996 &format_transposed);
1997 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1998 return false;
2001 return true;
2005 Shows the album name setting menu
2007 int album_name_menu(void)
2009 int selection = show_album_name;
2011 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2012 "Hide album title", "Show at the bottom", "Show at the top");
2013 rb->do_menu(&album_name_menu, &selection, NULL, false);
2015 show_album_name = selection;
2016 return GO_TO_PREVIOUS;
2020 Shows the settings menu
2022 int settings_menu(void)
2024 int selection = 0;
2025 bool old_val;
2027 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2028 "Spacing", "Centre margin", "Number of slides", "Zoom",
2029 "Show album title", "Resize Covers", "Rebuild cache");
2031 do {
2032 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2033 switch(selection) {
2034 case 0:
2035 rb->set_bool("Show FPS", &show_fps);
2036 reset_track_list();
2037 break;
2039 case 1:
2040 rb->set_int("Spacing between slides", "", 1,
2041 &slide_spacing,
2042 NULL, 1, 0, 100, NULL );
2043 recalc_offsets();
2044 reset_slides();
2045 break;
2047 case 2:
2048 rb->set_int("Centre margin", "", 1,
2049 &center_margin,
2050 NULL, 1, 0, 80, NULL );
2051 recalc_offsets();
2052 reset_slides();
2053 break;
2055 case 3:
2056 rb->set_int("Number of slides", "", 1, &num_slides,
2057 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2058 recalc_offsets();
2059 reset_slides();
2060 break;
2062 case 4:
2063 rb->set_int("Zoom", "", 1, &zoom,
2064 NULL, 1, 10, 300, NULL );
2065 recalc_offsets();
2066 reset_slides();
2067 break;
2068 case 5:
2069 album_name_menu();
2070 reset_track_list();
2071 recalc_offsets();
2072 reset_slides();
2073 break;
2074 case 6:
2075 old_val = resize;
2076 rb->set_bool("Resize Covers", &resize);
2077 if (old_val == resize) /* changed? */
2078 break;
2079 /* fallthrough if changed, since cache needs to be rebuilt */
2080 case 7:
2081 cache_version = 0;
2082 rb->remove(EMPTY_SLIDE);
2083 rb->splash(HZ, "Cache will be rebuilt on next restart");
2084 break;
2086 case MENU_ATTACHED_USB:
2087 return PLUGIN_USB_CONNECTED;
2089 } while ( selection >= 0 );
2090 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2091 return 0;
2095 Show the main menu
2097 int main_menu(void)
2099 int selection = 0;
2100 int result;
2102 #if LCD_DEPTH > 1
2103 rb->lcd_set_foreground(N_BRIGHT(255));
2104 #endif
2106 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2107 "Settings", "Return", "Quit");
2108 while (1) {
2109 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2110 case 0:
2111 result = settings_menu();
2112 if ( result != 0 ) return result;
2113 break;
2115 case 1:
2116 return 0;
2118 case 2:
2119 return -1;
2121 case MENU_ATTACHED_USB:
2122 return PLUGIN_USB_CONNECTED;
2124 default:
2125 return 0;
2131 Animation step for zooming into the current cover
2133 void update_cover_in_animation(void)
2135 cover_animation_keyframe++;
2136 if( cover_animation_keyframe < 20 ) {
2137 center_slide.distance-=5;
2138 center_slide.angle+=1;
2139 extra_fade += 13;
2141 else if( cover_animation_keyframe < 35 ) {
2142 center_slide.angle+=16;
2144 else {
2145 cover_animation_keyframe = 0;
2146 pf_state = pf_show_tracks;
2151 Animation step for zooming out the current cover
2153 void update_cover_out_animation(void)
2155 cover_animation_keyframe++;
2156 if( cover_animation_keyframe <= 15 ) {
2157 center_slide.angle-=16;
2159 else if( cover_animation_keyframe < 35 ) {
2160 center_slide.distance+=5;
2161 center_slide.angle-=1;
2162 extra_fade -= 13;
2164 else {
2165 cover_animation_keyframe = 0;
2166 pf_state = pf_idle;
2171 Draw a blue gradient at y with height h
2173 static inline void draw_gradient(int y, int h)
2175 static int r, inc, c;
2176 inc = (100 << 8) / h;
2177 c = 0;
2178 selected_track_pulse = (selected_track_pulse+1) % 10;
2179 int c2 = selected_track_pulse - 5;
2180 for (r=0; r<h; r++) {
2181 #ifdef HAVE_LCD_COLOR
2182 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2183 c2+250-(c >> 8)));
2184 #else
2185 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2186 #endif
2187 MYLCD(hline)(0, LCD_WIDTH, r+y);
2188 if ( r > h/2 )
2189 c-=inc;
2190 else
2191 c+=inc;
2196 static void track_list_yh(int char_height)
2198 switch (show_album_name)
2200 case album_name_hide:
2201 track_list_y = (show_fps ? char_height : 0);
2202 track_list_h = LCD_HEIGHT - track_list_y;
2203 break;
2204 case album_name_bottom:
2205 track_list_y = (show_fps ? char_height : 0);
2206 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2207 break;
2208 default: /* case album_name_top */
2209 track_list_y = char_height * 2;
2210 track_list_h = LCD_HEIGHT - track_list_y -
2211 (show_fps ? char_height : 0);
2212 break;
2217 Reset the track list after a album change
2219 void reset_track_list(void)
2221 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2222 track_list_yh(albumtxt_h);
2223 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2224 start_index_track_list = 0;
2225 track_scroll_index = 0;
2226 track_scroll_dir = 1;
2227 selected_track = 0;
2229 /* let the tracklist start more centered
2230 * if the screen isn't filled with tracks */
2231 if (track_count*albumtxt_h < track_list_h)
2233 track_list_h = track_count * albumtxt_h;
2234 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2239 Display the list of tracks
2241 void show_track_list(void)
2243 MYLCD(clear_display)();
2244 if ( center_slide.slide_index != track_index ) {
2245 create_track_index(center_slide.slide_index);
2246 reset_track_list();
2248 static int titletxt_w, titletxt_x, color, titletxt_h;
2249 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2251 int titletxt_y = track_list_y;
2252 int track_i;
2253 track_i = start_index_track_list;
2254 for (;track_i < track_list_visible_entries+start_index_track_list;
2255 track_i++)
2257 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2258 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2259 if ( track_i == selected_track ) {
2260 draw_gradient(titletxt_y, titletxt_h);
2261 MYLCD(set_foreground)(G_BRIGHT(255));
2262 if (titletxt_w > LCD_WIDTH ) {
2263 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2264 track_scroll_dir = 1;
2265 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2266 track_scroll_index += track_scroll_dir*2;
2267 titletxt_x = track_scroll_index;
2269 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2271 else {
2272 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2273 MYLCD(set_foreground)(G_BRIGHT(color));
2274 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2276 titletxt_y += titletxt_h;
2280 void select_next_track(void)
2282 if ( selected_track < track_count - 1 ) {
2283 selected_track++;
2284 track_scroll_index = 0;
2285 track_scroll_dir = 1;
2286 if (selected_track==(track_list_visible_entries+start_index_track_list))
2287 start_index_track_list++;
2291 void select_prev_track(void)
2293 if (selected_track > 0 ) {
2294 if (selected_track==start_index_track_list) start_index_track_list--;
2295 track_scroll_index = 0;
2296 track_scroll_dir = 1;
2297 selected_track--;
2302 Draw the current album name
2304 void draw_album_text(void)
2306 if (0 == show_album_name)
2307 return;
2309 int albumtxt_w, albumtxt_h;
2310 int albumtxt_y = 0;
2312 char *albumtxt;
2313 int c;
2314 /* Draw album text */
2315 if ( pf_state == pf_scrolling ) {
2316 c = ((slide_frame & 0xffff )/ 255);
2317 if (step < 0) c = 255-c;
2318 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2319 albumtxt = get_album_name(center_index+step);
2320 c = (c-128)*2;
2322 else {
2323 albumtxt = get_album_name(center_index);
2324 c = (128-c)*2;
2327 else {
2328 c= 255;
2329 albumtxt = get_album_name(center_index);
2332 MYLCD(set_foreground)(G_BRIGHT(c));
2333 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2334 if (center_index != prev_center_index) {
2335 albumtxt_x = 0;
2336 albumtxt_dir = -1;
2337 prev_center_index = center_index;
2340 if (show_album_name == album_name_top)
2341 albumtxt_y = albumtxt_h / 2;
2342 else
2343 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2345 if (albumtxt_w > LCD_WIDTH ) {
2346 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2347 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2348 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2349 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2350 albumtxt_x += albumtxt_dir;
2353 else {
2354 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2362 Main function that also contain the main plasma
2363 algorithm.
2365 int main(void)
2367 int ret;
2369 rb->lcd_setfont(FONT_UI);
2370 draw_splashscreen();
2372 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2373 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2374 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2375 return PLUGIN_ERROR;
2379 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2381 init_reflect_table();
2383 ALIGN_BUFFER(buf, buf_size, 4);
2384 ret = create_album_index();
2385 if (ret == ERROR_BUFFER_FULL) {
2386 rb->splash(HZ, "Not enough memory for album names");
2387 return PLUGIN_ERROR;
2388 } else if (ret == ERROR_NO_ALBUMS) {
2389 rb->splash(HZ, "No albums found. Please enable database");
2390 return PLUGIN_ERROR;
2393 ALIGN_BUFFER(buf, buf_size, 4);
2394 number_of_slides = album_count;
2395 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2396 rb->splash(HZ, "Could not create album art cache");
2397 return PLUGIN_ERROR;
2400 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2401 rb->splash(HZ, "Could not load the empty slide");
2402 return PLUGIN_ERROR;
2404 cache_version = CACHE_VERSION;
2405 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2408 #ifdef USEGSLIB
2409 long grey_buf_used;
2410 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2411 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2413 rb->splash(HZ, "Greylib init failed!");
2414 return PLUGIN_ERROR;
2416 grey_setfont(FONT_UI);
2417 buf_size -= grey_buf_used;
2418 buf = (void*)(grey_buf_used + (char*)buf);
2419 #endif
2420 buflib_init(&buf_ctx, (void *)buf, buf_size);
2422 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2424 rb->splash(HZ, "Unable to load empty slide image");
2425 return PLUGIN_ERROR;
2428 if (!create_pf_thread()) {
2429 rb->splash(HZ, "Cannot create thread!");
2430 return PLUGIN_ERROR;
2433 int i;
2435 /* initialize */
2436 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2437 cache[i].hid = 0;
2438 cache[i].index = 0;
2439 cache[i].next = i + 1;
2440 cache[i].prev = i - 1;
2442 cache[0].prev = i - 1;
2443 cache[i - 1].next = 0;
2444 cache_free = 0;
2445 buffer = LCD_BUF;
2447 pf_state = pf_idle;
2449 track_index = -1;
2450 extra_fade = 0;
2451 slide_frame = 0;
2452 step = 0;
2453 target = 0;
2454 fade = 256;
2456 recalc_offsets();
2457 reset_slides();
2459 char fpstxt[10];
2460 int button;
2462 int frames = 0;
2463 long last_update = *rb->current_tick;
2464 long current_update;
2465 long update_interval = 100;
2466 int fps = 0;
2467 int fpstxt_y;
2469 bool instant_update;
2470 #ifdef USEGSLIB
2471 grey_show(true);
2472 grey_set_drawmode(DRMODE_FG);
2473 #endif
2474 rb->lcd_set_drawmode(DRMODE_FG);
2475 while (true) {
2476 current_update = *rb->current_tick;
2477 frames++;
2479 /* Initial rendering */
2480 instant_update = false;
2482 /* Handle states */
2483 switch ( pf_state ) {
2484 case pf_scrolling:
2485 update_scroll_animation();
2486 render_all_slides();
2487 instant_update = true;
2488 break;
2489 case pf_cover_in:
2490 update_cover_in_animation();
2491 render_all_slides();
2492 instant_update = true;
2493 break;
2494 case pf_cover_out:
2495 update_cover_out_animation();
2496 render_all_slides();
2497 instant_update = true;
2498 break;
2499 case pf_show_tracks:
2500 show_track_list();
2501 break;
2502 case pf_idle:
2503 render_all_slides();
2504 break;
2507 /* Calculate FPS */
2508 if (current_update - last_update > update_interval) {
2509 fps = frames * HZ / (current_update - last_update);
2510 last_update = current_update;
2511 frames = 0;
2513 /* Draw FPS */
2514 if (show_fps)
2516 #ifdef USEGSLIB
2517 MYLCD(set_foreground)(G_BRIGHT(255));
2518 #else
2519 MYLCD(set_foreground)(G_PIX(255,0,0));
2520 #endif
2521 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2522 if (show_album_name == album_name_top)
2523 fpstxt_y = LCD_HEIGHT -
2524 rb->screens[SCREEN_MAIN]->getcharheight();
2525 else
2526 fpstxt_y = 0;
2527 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2529 draw_album_text();
2532 /* Copy offscreen buffer to LCD and give time to other threads */
2533 MYLCD(update)();
2534 rb->yield();
2536 /*/ Handle buttons */
2537 button = rb->get_custom_action(CONTEXT_CUSTOM|
2538 (pf_state == pf_show_tracks ? 1 : 0),
2539 instant_update ? 0 : HZ/16,
2540 get_context_map);
2542 switch (button) {
2543 case PF_QUIT:
2544 return PLUGIN_OK;
2546 case PF_BACK:
2547 if ( pf_state == pf_show_tracks )
2549 buflib_buffer_in(&buf_ctx, borrowed);
2550 borrowed = 0;
2551 track_index = -1;
2552 pf_state = pf_cover_out;
2554 if (pf_state == pf_idle || pf_state == pf_scrolling)
2555 return PLUGIN_OK;
2556 break;
2558 case PF_MENU:
2559 #ifdef USEGSLIB
2560 grey_show(false);
2561 #endif
2562 ret = main_menu();
2563 if ( ret == -1 ) return PLUGIN_OK;
2564 if ( ret != 0 ) return ret;
2565 #ifdef USEGSLIB
2566 grey_show(true);
2567 #endif
2568 MYLCD(set_drawmode)(DRMODE_FG);
2569 break;
2571 case PF_NEXT:
2572 case PF_NEXT_REPEAT:
2573 if ( pf_state == pf_show_tracks )
2574 select_next_track();
2575 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2576 show_next_slide();
2577 break;
2579 case PF_PREV:
2580 case PF_PREV_REPEAT:
2581 if ( pf_state == pf_show_tracks )
2582 select_prev_track();
2583 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2584 show_previous_slide();
2585 break;
2587 case PF_SELECT:
2588 if ( pf_state == pf_idle ) {
2589 pf_state = pf_cover_in;
2591 break;
2593 default:
2594 if (rb->default_event_handler_ex(button, cleanup, NULL)
2595 == SYS_USB_CONNECTED)
2596 return PLUGIN_USB_CONNECTED;
2597 break;
2604 /*************************** Plugin entry point ****************************/
2606 enum plugin_status plugin_start(const void *parameter)
2608 int ret;
2609 (void) parameter;
2610 #if LCD_DEPTH > 1
2611 rb->lcd_set_backdrop(NULL);
2612 #endif
2613 /* Turn off backlight timeout */
2614 backlight_force_on(); /* backlight control in lib/helper.c */
2615 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2616 rb->cpu_boost(true);
2617 #endif
2618 #if PLUGIN_BUFFER_SIZE > 0x10000
2619 buf = rb->plugin_get_buffer(&buf_size);
2620 #else
2621 buf = rb->plugin_get_audio_buffer(&buf_size);
2622 #endif
2623 ret = main();
2624 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2625 rb->cpu_boost(false);
2626 #endif
2627 if ( ret == PLUGIN_OK ) {
2628 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2629 CONFIG_VERSION))
2631 rb->splash(HZ, "Error writing config.");
2632 ret = PLUGIN_ERROR;
2636 end_pf_thread();
2637 cleanup(NULL);
2638 return ret;