Fix cover corruption reported in PictureFlow by Maurus Cuelenaere, bump CACHE_VERSION...
[maemo-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob2bf8c208024880991b28ec7e6c4c56d2fac1db96
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/picture.h"
33 #include "pluginbitmaps/pictureflow_logo.h"
34 #include "lib/grey.h"
35 #include "lib/feature_wrappers.h"
36 #include "lib/buflib.h"
38 PLUGIN_HEADER
40 /******************************* Globals ***********************************/
42 #define PF_PREV ACTION_STD_PREV
43 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
44 #define PF_NEXT ACTION_STD_NEXT
45 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
46 #define PF_SELECT ACTION_STD_OK
47 #define PF_CONTEXT ACTION_STD_CONTEXT
48 #define PF_BACK ACTION_STD_CANCEL
49 #define PF_MENU ACTION_STD_MENU
50 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
52 const struct button_mapping pf_context_album_scroll[] =
54 #ifdef HAVE_TOUCHSCREEN
55 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
56 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
57 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
58 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
59 #endif
60 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
61 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
62 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
63 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
64 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
65 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
66 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
67 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
68 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
69 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
70 CONFIG_KEYPAD == MEIZU_M6SL_PAD
71 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
72 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
73 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
74 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
75 #elif CONFIG_KEYPAD == ONDIO_PAD
76 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
77 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
79 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
80 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
81 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
82 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
83 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
84 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
85 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
86 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
87 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
88 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
89 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
90 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
91 #endif
92 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
95 const struct button_mapping pf_context_buttons[] =
97 #ifdef HAVE_TOUCHSCREEN
98 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
99 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
100 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
101 #endif
102 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
103 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
104 #elif CONFIG_KEYPAD == SANSA_C100_PAD
105 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
106 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
107 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
108 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
109 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
110 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
111 CONFIG_KEYPAD == SANSA_FUZE_PAD
112 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
113 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
115 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
116 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
117 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
118 #if CONFIG_KEYPAD == COWOND2_PAD
119 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
120 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
121 #endif
122 #elif CONFIG_KEYPAD == SANSA_E200_PAD
123 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
124 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
125 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
126 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
127 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
128 || (CONFIG_KEYPAD == IPOD_4G_PAD)
129 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
130 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
131 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
132 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
133 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
134 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
135 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
136 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
137 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
138 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
139 #endif
140 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
142 #else
143 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
144 #endif
146 const struct button_mapping *pf_contexts[] =
148 pf_context_album_scroll,
149 pf_context_buttons
152 #if LCD_DEPTH < 8
153 #if LCD_DEPTH > 1
154 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
155 #else /* LCD_DEPTH <= 1 */
156 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
157 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
158 #define PICTUREFLOW_DRMODE DRMODE_SOLID
159 #else
160 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
161 #endif
162 #endif /* LCD_DEPTH <= 1 */
163 #define USEGSLIB
164 GREY_INFO_STRUCT
165 #define LCD_BUF _grey_info.buffer
166 #define MYLCD(fn) grey_ ## fn
167 #define G_PIX(r,g,b) \
168 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
169 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
170 #define G_BRIGHT(y) (y)
171 #define BUFFER_WIDTH _grey_info.width
172 #define BUFFER_HEIGHT _grey_info.height
173 typedef unsigned char pix_t;
174 #else /* LCD_DEPTH >= 8 */
175 #define LCD_BUF rb->lcd_framebuffer
176 #define MYLCD(fn) rb->lcd_ ## fn
177 #define G_PIX LCD_RGBPACK
178 #define N_PIX LCD_RGBPACK
179 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
180 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
181 #define BUFFER_WIDTH LCD_WIDTH
182 #define BUFFER_HEIGHT LCD_HEIGHT
183 typedef fb_data pix_t;
184 #endif /* LCD_DEPTH >= 8 */
186 /* for fixed-point arithmetic, we need minimum 32-bit long
187 long long (64-bit) might be useful for multiplication and division */
188 #define PFreal long
189 #define PFREAL_SHIFT 10
190 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
191 #define PFREAL_ONE (1 << PFREAL_SHIFT)
192 #define PFREAL_HALF (PFREAL_ONE >> 1)
195 #define IANGLE_MAX 1024
196 #define IANGLE_MASK 1023
198 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
199 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
200 #define DISPLAY_HEIGHT REFLECT_TOP
201 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
202 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
203 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
204 (REFLECT_HEIGHT * 5))
205 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
206 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
207 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
208 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
209 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
211 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
213 #define MAX_SLIDES_COUNT 10
215 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
216 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
218 #define EV_EXIT 9999
219 #define EV_WAKEUP 1337
221 #define UNIQBUF_SIZE (64*1024)
223 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
224 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
226 /* Error return values */
227 #define ERROR_NO_ALBUMS -1
228 #define ERROR_BUFFER_FULL -2
230 /* current version for cover cache */
231 #define CACHE_VERSION 3
232 #define CONFIG_VERSION 1
233 #define CONFIG_FILE "pictureflow.cfg"
235 /** structs we use */
237 struct slide_data {
238 int slide_index;
239 int angle;
240 PFreal cx;
241 PFreal cy;
242 PFreal distance;
245 struct slide_cache {
246 int index; /* index of the cached slide */
247 int hid; /* handle ID of the cached slide */
248 short next; /* "next" slide, with LRU last */
249 short prev; /* "previous" slide */
252 struct album_data {
253 int name_idx;
254 long seek;
257 struct track_data {
258 uint32_t sort;
259 int name_idx;
260 long seek;
263 struct rect {
264 int left;
265 int right;
266 int top;
267 int bottom;
270 struct load_slide_event_data {
271 int slide_index;
272 int cache_index;
276 struct pfraw_header {
277 int32_t width; /* bmap width in pixels */
278 int32_t height; /* bmap height in pixels */
281 const struct picture logos[]={
282 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
285 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
286 album_name_top };
287 static char* show_album_name_conf[] =
289 "hide",
290 "bottom",
291 "top"
294 #define MAX_SPACING 40
295 #define MAX_MARGIN 80
297 /* config values and their defaults */
298 static int slide_spacing = DISPLAY_WIDTH / 4;
299 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
300 static int num_slides = 4;
301 static int zoom = 100;
302 static bool show_fps = false;
303 static bool resize = true;
304 static int cache_version = 0;
305 static int show_album_name = (LCD_HEIGHT > 100)
306 ? album_name_top : album_name_bottom;
308 static struct configdata config[] =
310 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
311 NULL },
312 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
313 NULL },
314 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
315 NULL },
316 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
317 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
318 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
319 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
320 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
321 show_album_name_conf }
324 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
326 /** below we allocate the memory we want to use **/
328 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
329 static uint8_t reflect_table[REFLECT_HEIGHT];
330 static struct slide_data center_slide;
331 static struct slide_data left_slides[MAX_SLIDES_COUNT];
332 static struct slide_data right_slides[MAX_SLIDES_COUNT];
333 static int slide_frame;
334 static int step;
335 static int target;
336 static int fade;
337 static int center_index = 0; /* index of the slide that is in the center */
338 static int itilt;
339 static PFreal offsetX;
340 static PFreal offsetY;
341 static int number_of_slides;
343 static struct slide_cache cache[SLIDE_CACHE_SIZE];
344 static int cache_free;
345 static int cache_used = -1;
346 static int cache_left_index = -1;
347 static int cache_right_index = -1;
348 static int cache_center_index = -1;
350 /* use long for aligning */
351 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
352 /* queue (as array) for scheduling load_surface */
354 static int empty_slide_hid;
356 unsigned int thread_id;
357 struct event_queue thread_q;
359 static struct tagcache_search tcs;
361 static struct buflib_context buf_ctx;
363 static struct album_data *album;
364 static char *album_names;
365 static int album_count;
367 static struct track_data *tracks;
368 static char *track_names;
369 static size_t borrowed = 0;
370 static int track_count;
371 static int track_index;
372 static int selected_track;
373 static int selected_track_pulse;
374 void reset_track_list(void);
376 void * buf;
377 size_t buf_size;
379 static bool thread_is_running;
381 static int cover_animation_keyframe;
382 static int extra_fade;
384 static int albumtxt_x = 0;
385 static int albumtxt_dir = -1;
386 static int prev_center_index = -1;
388 static int start_index_track_list = 0;
389 static int track_list_visible_entries = 0;
390 static int track_list_y;
391 static int track_list_h;
392 static int track_scroll_index = 0;
393 static int track_scroll_dir = 1;
396 Proposals for transitions:
398 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
399 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
401 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
403 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
405 TODO:
406 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
407 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
409 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
411 enum pf_states {
412 pf_idle = 0,
413 pf_scrolling,
414 pf_cover_in,
415 pf_show_tracks,
416 pf_cover_out
419 static int pf_state;
421 /** code */
422 static bool free_slide_prio(int prio);
423 static inline unsigned fade_color(pix_t c, unsigned a);
424 bool save_pfraw(char* filename, struct bitmap *bm);
425 bool load_new_slide(void);
426 int load_surface(int);
428 static inline PFreal fmul(PFreal a, PFreal b)
430 return (a*b) >> PFREAL_SHIFT;
434 * This version preshifts each operand, which is useful when we know how many
435 * of the least significant bits will be empty, or are worried about overflow
436 * in a particular calculation
438 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
440 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
443 /* ARMv5+ has a clz instruction equivalent to our function.
445 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
446 static inline int clz(uint32_t v)
448 return __builtin_clz(v);
451 /* Otherwise, use our clz, which can be inlined */
452 #elif defined(CPU_COLDFIRE)
453 /* This clz is based on the log2(n) implementation at
454 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
455 * A clz benchmark plugin showed this to be about 14% faster on coldfire
456 * than the LUT-based version.
458 static inline int clz(uint32_t v)
460 int r = 32;
461 if (v >= 0x10000)
463 v >>= 16;
464 r -= 16;
466 if (v & 0xff00)
468 v >>= 8;
469 r -= 8;
471 if (v & 0xf0)
473 v >>= 4;
474 r -= 4;
476 if (v & 0xc)
478 v >>= 2;
479 r -= 2;
481 if (v & 2)
483 v >>= 1;
484 r -= 1;
486 r -= v;
487 return r;
489 #else
490 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
491 0, 0, 0, 0, 0, 0, 0, 0 };
492 /* This clz is based on the log2(n) implementation at
493 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
494 * It is not any faster than the one above, but trades 16B in the lookup table
495 * for a savings of 12B per each inlined call.
497 static inline int clz(uint32_t v)
499 int r = 28;
500 if (v >= 0x10000)
502 v >>= 16;
503 r -= 16;
505 if (v & 0xff00)
507 v >>= 8;
508 r -= 8;
510 if (v & 0xf0)
512 v >>= 4;
513 r -= 4;
515 return r + clz_lut[v];
517 #endif
519 /* Return the maximum possible left shift for a signed int32, without
520 * overflow
522 static inline int allowed_shift(int32_t val)
524 uint32_t uval = val ^ (val >> 31);
525 return clz(uval) - 1;
528 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
529 * num and den before dividing.
531 static inline PFreal fdiv(PFreal num, PFreal den)
533 int shift = allowed_shift(num);
534 shift = MIN(PFREAL_SHIFT, shift);
535 num <<= shift;
536 den >>= PFREAL_SHIFT - shift;
537 return num / den;
540 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
541 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
542 #define fabs(a) (a < 0 ? -a : a)
543 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
545 #if CONFIG_CPU == SH7034
546 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
547 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
548 #else
549 #define MULUQ(a, b) ((a) * (b))
550 #endif
553 #if 0
554 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
555 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
557 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
558 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
560 static inline PFreal fmul(PFreal a, PFreal b)
562 return (a*b) >> PFREAL_SHIFT;
565 static inline PFreal fdiv(PFreal n, PFreal m)
567 return (n<<(PFREAL_SHIFT))/m;
569 #endif
571 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
572 static const short sin_tab[] = {
573 0, 100, 200, 297, 392, 483, 569, 650,
574 724, 792, 851, 903, 946, 980, 1004, 1019,
575 1024, 1019, 1004, 980, 946, 903, 851, 792,
576 724, 650, 569, 483, 392, 297, 200, 100,
577 0, -100, -200, -297, -392, -483, -569, -650,
578 -724, -792, -851, -903, -946, -980, -1004, -1019,
579 -1024, -1019, -1004, -980, -946, -903, -851, -792,
580 -724, -650, -569, -483, -392, -297, -200, -100,
584 static inline PFreal fsin(int iangle)
586 iangle &= IANGLE_MASK;
588 int i = (iangle >> 4);
589 PFreal p = sin_tab[i];
590 PFreal q = sin_tab[(i+1)];
591 PFreal g = (q - p);
592 return p + g * (iangle-i*16)/16;
595 static inline PFreal fcos(int iangle)
597 return fsin(iangle + (IANGLE_MAX >> 2));
600 static inline uint32_t div255(uint32_t val)
602 return ((((val >> 8) + val) >> 8) + val) >> 8;
605 #define SCALE_VAL(val,out) div255((val) * (out) + 127)
606 #define SCALE_VAL32(val, out) \
607 ({ \
608 uint32_t val__ = (val) * (out); \
609 val__ = ((((val__ >> 8) + val__) >> 8) + val__ + 128) >> 8; \
610 val__; \
612 #define SCALE_VAL8(val, out) \
613 ({ \
614 unsigned val__ = (val) * (out); \
615 val__ = ((val__ >> 8) + val__ + 128) >> 8; \
616 val__; \
619 static void output_row_8_transposed(uint32_t row, void * row_in,
620 struct scaler_context *ctx)
622 pix_t *dest = (pix_t*)ctx->bm->data + row;
623 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
624 #ifdef USEGSLIB
625 uint8_t *qp = (uint8_t*)row_in;
626 for (; dest < end; dest += ctx->bm->height)
627 *dest = *qp++;
628 #else
629 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
630 unsigned r, g, b;
631 for (; dest < end; dest += ctx->bm->height)
633 r = SCALE_VAL8(qp->red, 31);
634 g = SCALE_VAL8(qp->green, 63);
635 b = SCALE_VAL8((qp++)->blue, 31);
636 *dest = LCD_RGBPACK_LCD(r,g,b);
638 #endif
641 static void output_row_32_transposed(uint32_t row, void * row_in,
642 struct scaler_context *ctx)
644 pix_t *dest = (pix_t*)ctx->bm->data + row;
645 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
646 #ifdef USEGSLIB
647 uint32_t *qp = (uint32_t*)row_in;
648 for (; dest < end; dest += ctx->bm->height)
649 *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor);
650 #else
651 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
652 uint32_t rb_mul = SCALE_VAL32(ctx->divisor, 31),
653 rb_rnd = SCALE_VAL32(ctx->round, 31),
654 g_mul = SCALE_VAL32(ctx->divisor, 63),
655 g_rnd = SCALE_VAL32(ctx->round, 63);
656 int r, g, b;
657 for (; dest < end; dest += ctx->bm->height)
659 r = SC_MUL(qp->r + rb_rnd, rb_mul);
660 g = SC_MUL(qp->g + g_rnd, g_mul);
661 b = SC_MUL(qp->b + rb_rnd, rb_mul);
662 qp++;
663 *dest = LCD_RGBPACK_LCD(r,g,b);
665 #endif
668 #ifdef HAVE_LCD_COLOR
669 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
670 struct scaler_context *ctx)
672 pix_t *dest = (pix_t*)ctx->bm->data + row;
673 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
674 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
675 for (; dest < end; dest += ctx->bm->height)
677 unsigned r, g, b, y, u, v;
678 y = SC_MUL(qp->b + ctx->round, ctx->divisor);
679 u = SC_MUL(qp->g + ctx->round, ctx->divisor);
680 v = SC_MUL(qp->r + ctx->round, ctx->divisor);
681 qp++;
682 yuv_to_rgb(y, u, v, &r, &g, &b);
683 r = (31 * r + (r >> 3) + 127) >> 8;
684 g = (63 * g + (g >> 2) + 127) >> 8;
685 b = (31 * b + (b >> 3) + 127) >> 8;
686 *dest = LCD_RGBPACK_LCD(r, g, b);
689 #endif
691 static unsigned int get_size(struct bitmap *bm)
693 return bm->width * bm->height * sizeof(pix_t);
696 const struct custom_format format_transposed = {
697 .output_row_8 = output_row_8_transposed,
698 #ifdef HAVE_LCD_COLOR
699 .output_row_32 = {
700 output_row_32_transposed,
701 output_row_32_transposed_fromyuv
703 #else
704 .output_row_32 = output_row_32_transposed,
705 #endif
706 .get_size = get_size
709 static const struct button_mapping* get_context_map(int context)
711 return pf_contexts[context & ~CONTEXT_CUSTOM];
714 /* Create the lookup table with the scaling values for the reflections */
715 void init_reflect_table(void)
717 int i;
718 for (i = 0; i < REFLECT_HEIGHT; i++)
719 reflect_table[i] =
720 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
721 (5 * REFLECT_HEIGHT);
725 Create an index of all albums from the database.
726 Also store the album names so we can access them later.
728 int create_album_index(void)
730 buf_size -= UNIQBUF_SIZE * sizeof(long);
731 long *uniqbuf = (long *)(buf_size + (char *)buf);
732 album = ((struct album_data *)uniqbuf) - 1;
733 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
734 album_count = 0;
735 rb->tagcache_search(&tcs, tag_album);
736 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
737 unsigned int l, old_l = 0;
738 album_names = buf;
739 album[0].name_idx = 0;
740 while (rb->tagcache_get_next(&tcs))
742 buf_size -= sizeof(struct album_data);
743 l = tcs.result_len;
744 if ( album_count > 0 )
745 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
747 if ( l > buf_size )
748 /* not enough memory */
749 return ERROR_BUFFER_FULL;
751 rb->strcpy(buf, tcs.result);
752 buf_size -= l;
753 buf = l + (char *)buf;
754 album[-album_count].seek = tcs.result_seek;
755 old_l = l;
756 album_count++;
758 rb->tagcache_search_finish(&tcs);
759 ALIGN_BUFFER(buf, buf_size, 4);
760 int i;
761 struct album_data* tmp_album = (struct album_data*)buf;
762 for (i = album_count - 1; i >= 0; i--)
763 tmp_album[i] = album[-i];
764 album = tmp_album;
765 buf = album + album_count;
766 buf_size += UNIQBUF_SIZE * sizeof(long);
767 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
771 Return a pointer to the album name of the given slide_index
773 char* get_album_name(const int slide_index)
775 return album_names + album[slide_index].name_idx;
779 Return a pointer to the track name of the active album
780 create_track_index has to be called first.
782 char* get_track_name(const int track_index)
784 if ( track_index < track_count )
785 return track_names + tracks[track_index].name_idx;
786 return 0;
790 Compare two unsigned ints passed via pointers.
792 int compare_tracks (const void *a_v, const void *b_v)
794 uint32_t a = ((struct track_data *)a_v)->sort;
795 uint32_t b = ((struct track_data *)b_v)->sort;
796 return (int)(a - b);
800 Create the track index of the given slide_index.
802 void create_track_index(const int slide_index)
804 if ( slide_index == track_index )
805 return;
806 track_index = slide_index;
808 if (!rb->tagcache_search(&tcs, tag_title))
809 goto fail;
811 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
812 track_count=0;
813 int string_index = 0, track_num;
814 int disc_num;
815 size_t out = 0;
816 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
817 borrowed += out;
818 int avail = borrowed;
819 tracks = (struct track_data*)(track_names + borrowed);
820 while (rb->tagcache_get_next(&tcs))
822 avail -= sizeof(struct track_data);
823 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
824 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
825 int len = 0;
826 if (disc_num < 0)
827 disc_num = 0;
828 retry:
829 if (track_num >= 0)
831 if (disc_num)
832 len = 1 + rb->snprintf(track_names + string_index , avail,
833 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
834 else
835 len = 1 + rb->snprintf(track_names + string_index , avail,
836 "%d: %s", track_num + 1, tcs.result);
838 else
840 track_num = 0;
841 len = tcs.result_len;
842 rb->strncpy(track_names + string_index, tcs.result, avail);
844 if (len > avail)
846 while (len > avail)
848 if (!free_slide_prio(0))
849 goto fail;
850 out = 0;
851 buflib_buffer_out(&buf_ctx, &out);
852 avail += out;
853 borrowed += out;
854 if (track_count)
856 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
857 unsigned int bytes = track_count * sizeof(struct track_data);
858 rb->memmove(new_tracks, tracks, bytes);
859 tracks = new_tracks;
862 goto retry;
865 avail -= len;
866 tracks--;
867 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
868 tracks->name_idx = string_index;
869 tracks->seek = tcs.result_seek;
870 track_count++;
871 string_index += len;
874 rb->tagcache_search_finish(&tcs);
876 /* now fix the track list order */
877 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
878 return;
879 fail:
880 track_count = 0;
881 return;
885 Determine filename of the album art for the given slide_index and
886 store the result in buf.
887 The algorithm looks for the first track of the given album uses
888 find_albumart to find the filename.
890 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
891 int buflen)
893 if ( slide_index == -1 )
895 rb->strncpy( buf, EMPTY_SLIDE, buflen );
898 if (!rb->tagcache_search(&tcs, tag_filename))
899 return false;
901 bool result;
902 /* find the first track of the album */
903 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
905 if ( rb->tagcache_get_next(&tcs) ) {
906 struct mp3entry id3;
907 int fd;
909 fd = rb->open(tcs.result, O_RDONLY);
910 rb->get_metadata(&id3, fd, tcs.result);
911 rb->close(fd);
912 if ( search_albumart_files(&id3, "", buf, buflen) )
913 result = true;
914 else
915 result = false;
917 else {
918 /* did not find a matching track */
919 result = false;
921 rb->tagcache_search_finish(&tcs);
922 return result;
926 Draw the PictureFlow logo
928 void draw_splashscreen(void)
930 struct screen* display = rb->screens[0];
931 const struct picture* logo = &(logos[display->screen_type]);
933 #if LCD_DEPTH > 1
934 rb->lcd_set_background(N_BRIGHT(0));
935 rb->lcd_set_foreground(N_BRIGHT(255));
936 #else
937 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
938 #endif
939 rb->lcd_clear_display();
941 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
942 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
943 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
944 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
945 #else
946 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
947 #endif
949 rb->lcd_update();
954 Draw a simple progress bar
956 void draw_progressbar(int step)
958 int txt_w, txt_h;
959 const int bar_height = 22;
960 const int w = LCD_WIDTH - 20;
961 const int x = 10;
963 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
965 int y = (LCD_HEIGHT - txt_h)/2;
967 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
968 y += (txt_h + 5);
970 #if LCD_DEPTH > 1
971 rb->lcd_set_foreground(N_BRIGHT(100));
972 #endif
973 rb->lcd_drawrect(x, y, w+2, bar_height);
974 #if LCD_DEPTH > 1
975 rb->lcd_set_foreground(N_PIX(165, 231, 82));
976 #endif
978 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
979 #if LCD_DEPTH > 1
980 rb->lcd_set_foreground(N_BRIGHT(255));
981 #endif
982 rb->lcd_update();
983 rb->yield();
987 Precomupte the album art images and store them in CACHE_PREFIX.
989 bool create_albumart_cache(void)
991 int ret;
993 int i, slides = 0;
994 struct bitmap input_bmp;
996 char pfraw_file[MAX_PATH];
997 char albumart_file[MAX_PATH];
998 unsigned int format = FORMAT_NATIVE;
999 cache_version = 0;
1000 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1001 if (resize)
1002 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1003 for (i=0; i < album_count; i++)
1005 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1007 /* delete existing cache, so it's a true rebuild */
1008 if(rb->file_exists(pfraw_file))
1009 rb->remove(pfraw_file);
1010 draw_progressbar(i);
1011 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1012 continue;
1014 input_bmp.data = buf;
1015 input_bmp.width = DISPLAY_WIDTH;
1016 input_bmp.height = DISPLAY_HEIGHT;
1017 ret = read_image_file(albumart_file, &input_bmp,
1018 buf_size, format, &format_transposed);
1019 if (ret <= 0) {
1020 rb->splash(HZ, "Could not read bmp");
1021 continue; /* skip missing/broken files */
1023 if (!save_pfraw(pfraw_file, &input_bmp))
1025 rb->splash(HZ, "Could not write bmp");
1027 slides++;
1028 if ( rb->button_get(false) == PF_MENU ) return false;
1030 if ( slides == 0 ) {
1031 /* Warn the user that we couldn't find any albumart */
1032 rb->splash(2*HZ, "No album art found");
1033 return false;
1035 return true;
1039 Thread used for loading and preparing bitmaps in the background
1041 void thread(void)
1043 long sleep_time = 5 * HZ;
1044 struct queue_event ev;
1045 while (1) {
1046 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1047 switch (ev.id) {
1048 case EV_EXIT:
1049 return;
1050 case EV_WAKEUP:
1051 /* we just woke up */
1052 break;
1054 while ( load_new_slide() ) {
1055 rb->yield();
1056 switch (ev.id) {
1057 case EV_EXIT:
1058 return;
1066 End the thread by posting the EV_EXIT event
1068 void end_pf_thread(void)
1070 if ( thread_is_running ) {
1071 rb->queue_post(&thread_q, EV_EXIT, 0);
1072 rb->thread_wait(thread_id);
1073 /* remove the thread's queue from the broadcast list */
1074 rb->queue_delete(&thread_q);
1075 thread_is_running = false;
1082 Create the thread an setup the event queue
1084 bool create_pf_thread(void)
1086 /* put the thread's queue in the bcast list */
1087 rb->queue_init(&thread_q, true);
1088 if ((thread_id = rb->create_thread(
1089 thread,
1090 thread_stack,
1091 sizeof(thread_stack),
1093 "Picture load thread"
1094 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1095 PRIORITY_REALTIME + 1))
1096 IF_COP(, CPU)
1098 ) == 0) {
1099 return false;
1101 thread_is_running = true;
1102 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1103 return true;
1107 Safe the given bitmap as filename in the pfraw format
1109 bool save_pfraw(char* filename, struct bitmap *bm)
1111 struct pfraw_header bmph;
1112 bmph.width = bm->width;
1113 bmph.height = bm->height;
1114 int fh = rb->creat( filename );
1115 if( fh < 0 ) return false;
1116 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1117 int y;
1118 for( y = 0; y < bm->height; y++ )
1120 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1121 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1123 rb->close( fh );
1124 return true;
1129 * The following functions implement the linked-list-in-array used to manage
1130 * the LRU cache of slides, and the list of free cache slots.
1133 #define seek_right_while(start, cond) \
1134 ({ \
1135 int ind_, next_ = (start); \
1136 do { \
1137 ind_ = next_; \
1138 next_ = cache[ind_].next; \
1139 } while (next_ != cache_used && (cond)); \
1140 ind_; \
1143 #define seek_left_while(start, cond) \
1144 ({ \
1145 int ind_, next_ = (start); \
1146 do { \
1147 ind_ = next_; \
1148 next_ = cache[ind_].prev; \
1149 } while (ind_ != cache_used && (cond)); \
1150 ind_; \
1154 Pop the given item from the linked list starting at *head, returning the next
1155 item, or -1 if the list is now empty.
1157 static inline int lla_pop_item (int *head, int i)
1159 int prev = cache[i].prev;
1160 int next = cache[i].next;
1161 if (i == next)
1163 *head = -1;
1164 return -1;
1166 else if (i == *head)
1167 *head = next;
1168 cache[next].prev = prev;
1169 cache[prev].next = next;
1170 return next;
1175 Pop the head item from the list starting at *head, returning the index of the
1176 item, or -1 if the list is already empty.
1178 static inline int lla_pop_head (int *head)
1180 int i = *head;
1181 if (i != -1)
1182 lla_pop_item(head, i);
1183 return i;
1187 Insert the item at index i before the one at index p.
1189 static inline void lla_insert (int i, int p)
1191 int next = p;
1192 int prev = cache[next].prev;
1193 cache[next].prev = i;
1194 cache[prev].next = i;
1195 cache[i].next = next;
1196 cache[i].prev = prev;
1201 Insert the item at index i at the end of the list starting at *head.
1203 static inline void lla_insert_tail (int *head, int i)
1205 if (*head == -1)
1207 *head = i;
1208 cache[i].next = i;
1209 cache[i].prev = i;
1210 } else
1211 lla_insert(i, *head);
1215 Insert the item at index i before the one at index p.
1217 static inline void lla_insert_after(int i, int p)
1219 p = cache[p].next;
1220 lla_insert(i, p);
1225 Insert the item at index i before the one at index p in the list starting at
1226 *head
1228 static inline void lla_insert_before(int *head, int i, int p)
1230 lla_insert(i, p);
1231 if (*head == p)
1232 *head = i;
1237 Free the used slide at index i, and its buffer, and move it to the free
1238 slides list.
1240 static inline void free_slide(int i)
1242 if (cache[i].hid != empty_slide_hid)
1243 buflib_free(&buf_ctx, cache[i].hid);
1244 cache[i].index = -1;
1245 lla_pop_item(&cache_used, i);
1246 lla_insert_tail(&cache_free, i);
1247 if (cache_used == -1)
1249 cache_right_index = -1;
1250 cache_left_index = -1;
1251 cache_center_index = -1;
1257 Free one slide ranked above the given priority. If no such slide can be found,
1258 return false.
1260 static bool free_slide_prio(int prio)
1262 if (cache_used == -1)
1263 return false;
1264 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1265 int prio_l = cache[l].index < center_index ?
1266 center_index - cache[l].index : 0;
1267 int prio_r = cache[r].index > center_index ?
1268 cache[r].index - center_index : 0;
1269 if (prio_l > prio_r)
1271 i = l;
1272 prio_max = prio_l;
1273 } else {
1274 i = r;
1275 prio_max = prio_r;
1277 if (prio_max > prio)
1279 if (i == cache_left_index)
1280 cache_left_index = cache[i].next;
1281 if (i == cache_right_index)
1282 cache_right_index = cache[i].prev;
1283 free_slide(i);
1284 return true;
1285 } else
1286 return false;
1290 Read the pfraw image given as filename and return the hid of the buffer
1292 int read_pfraw(char* filename, int prio)
1294 struct pfraw_header bmph;
1295 int fh = rb->open(filename, O_RDONLY);
1296 if( fh < 0 )
1297 return empty_slide_hid;
1298 else
1299 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1301 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1302 bmph.width * bmph.height;
1304 int hid;
1305 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1307 if (!hid) {
1308 rb->close( fh );
1309 return 0;
1312 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1314 bm->width = bmph.width;
1315 bm->height = bmph.height;
1316 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1318 int y;
1319 for( y = 0; y < bm->height; y++ )
1321 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1322 data += bm->width;
1324 rb->close( fh );
1325 return hid;
1330 Load the surface for the given slide_index into the cache at cache_index.
1332 static inline bool load_and_prepare_surface(const int slide_index,
1333 const int cache_index,
1334 const int prio)
1336 char tmp_path_name[MAX_PATH+1];
1337 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1338 slide_index);
1340 int hid = read_pfraw(tmp_path_name, prio);
1341 if (!hid)
1342 return false;
1344 cache[cache_index].hid = hid;
1346 if ( cache_index < SLIDE_CACHE_SIZE ) {
1347 cache[cache_index].index = slide_index;
1350 return true;
1355 Load the "next" slide that we can load, freeing old slides if needed, provided
1356 that they are further from center_index than the current slide
1358 bool load_new_slide(void)
1360 int i = -1;
1361 if (cache_center_index != -1)
1363 int next, prev;
1364 if (cache[cache_center_index].index != center_index)
1366 if (cache[cache_center_index].index < center_index)
1368 cache_center_index = seek_right_while(cache_center_index,
1369 cache[next_].index <= center_index);
1370 prev = cache_center_index;
1371 next = cache[cache_center_index].next;
1373 else
1375 cache_center_index = seek_left_while(cache_center_index,
1376 cache[next_].index >= center_index);
1377 next = cache_center_index;
1378 prev = cache[cache_center_index].prev;
1380 if (cache[cache_center_index].index != center_index)
1382 if (cache_free == -1)
1383 free_slide_prio(0);
1384 i = lla_pop_head(&cache_free);
1385 if (!load_and_prepare_surface(center_index, i, 0))
1386 goto fail_and_refree;
1387 if (cache[next].index == -1)
1389 if (cache[prev].index == -1)
1390 goto insert_first_slide;
1391 else
1392 next = cache[prev].next;
1394 lla_insert(i, next);
1395 if (cache[i].index < cache[cache_used].index)
1396 cache_used = i;
1397 cache_center_index = i;
1398 cache_left_index = i;
1399 cache_right_index = i;
1400 return true;
1403 if (cache[cache_left_index].index >
1404 cache[cache_center_index].index)
1405 cache_left_index = cache_center_index;
1406 if (cache[cache_right_index].index <
1407 cache[cache_center_index].index)
1408 cache_right_index = cache_center_index;
1409 cache_left_index = seek_left_while(cache_left_index,
1410 cache[ind_].index - 1 == cache[next_].index);
1411 cache_right_index = seek_right_while(cache_right_index,
1412 cache[ind_].index - 1 == cache[next_].index);
1413 int prio_l = cache[cache_center_index].index -
1414 cache[cache_left_index].index + 1;
1415 int prio_r = cache[cache_right_index].index -
1416 cache[cache_center_index].index + 1;
1417 if ((prio_l < prio_r ||
1418 cache[cache_right_index].index >= number_of_slides) &&
1419 cache[cache_left_index].index > 0)
1421 if (cache_free == -1 && !free_slide_prio(prio_l))
1422 return false;
1423 i = lla_pop_head(&cache_free);
1424 if (load_and_prepare_surface(cache[cache_left_index].index
1425 - 1, i, prio_l))
1427 lla_insert_before(&cache_used, i, cache_left_index);
1428 cache_left_index = i;
1429 return true;
1431 } else if(cache[cache_right_index].index < number_of_slides - 1)
1433 if (cache_free == -1 && !free_slide_prio(prio_r))
1434 return false;
1435 i = lla_pop_head(&cache_free);
1436 if (load_and_prepare_surface(cache[cache_right_index].index
1437 + 1, i, prio_r))
1439 lla_insert_after(i, cache_right_index);
1440 cache_right_index = i;
1441 return true;
1444 } else {
1445 i = lla_pop_head(&cache_free);
1446 if (load_and_prepare_surface(center_index, i, 0))
1448 insert_first_slide:
1449 cache[i].next = i;
1450 cache[i].prev = i;
1451 cache_center_index = i;
1452 cache_left_index = i;
1453 cache_right_index = i;
1454 cache_used = i;
1455 return true;
1458 fail_and_refree:
1459 if (i != -1)
1461 lla_insert_tail(&cache_free, i);
1463 return false;
1468 Get a slide from the buffer
1470 static inline struct dim *get_slide(const int hid)
1472 if (!hid)
1473 return NULL;
1475 struct dim *bmp;
1477 bmp = buflib_get_data(&buf_ctx, hid);
1479 return bmp;
1484 Return the requested surface
1486 static inline struct dim *surface(const int slide_index)
1488 if (slide_index < 0)
1489 return 0;
1490 if (slide_index >= number_of_slides)
1491 return 0;
1492 int i;
1493 if ((i = cache_used ) != -1)
1495 do {
1496 if (cache[i].index == slide_index)
1497 return get_slide(cache[i].hid);
1498 i = cache[i].next;
1499 } while (i != cache_used);
1501 return get_slide(empty_slide_hid);
1505 adjust slides so that they are in "steady state" position
1507 void reset_slides(void)
1509 center_slide.angle = 0;
1510 center_slide.cx = 0;
1511 center_slide.cy = 0;
1512 center_slide.distance = 0;
1513 center_slide.slide_index = center_index;
1515 int i;
1516 for (i = 0; i < num_slides; i++) {
1517 struct slide_data *si = &left_slides[i];
1518 si->angle = itilt;
1519 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1520 si->cy = offsetY;
1521 si->slide_index = center_index - 1 - i;
1522 si->distance = 0;
1525 for (i = 0; i < num_slides; i++) {
1526 struct slide_data *si = &right_slides[i];
1527 si->angle = -itilt;
1528 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1529 si->cy = offsetY;
1530 si->slide_index = center_index + 1 + i;
1531 si->distance = 0;
1537 Updates look-up table and other stuff necessary for the rendering.
1538 Call this when the viewport size or slide dimension is changed.
1540 * To calculate the offset that will provide the proper margin, we use the same
1541 * projection used to render the slides. The solution for xc, the slide center,
1542 * is:
1543 * xp * (zo + xs * sin(r))
1544 * xc = xp - xs * cos(r) + ───────────────────────
1546 * TODO: support moving the side slides toward or away from the camera
1548 void recalc_offsets(void)
1550 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1551 PFreal zo;
1552 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1553 PFREAL_ONE) * zoom / 100;
1554 PFreal cosr, sinr;
1556 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1557 cosr = fcos(-itilt);
1558 sinr = fsin(-itilt);
1559 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1560 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1561 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1562 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1563 / CAM_DIST;
1564 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1569 Fade the given color by spreading the fb_data (ushort)
1570 to an uint, multiply and compress the result back to a ushort.
1572 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1573 static inline unsigned fade_color(pix_t c, unsigned a)
1575 unsigned int result;
1576 c = swap16(c);
1577 a = (a + 2) & 0x1fc;
1578 result = ((c & 0xf81f) * a) & 0xf81f00;
1579 result |= ((c & 0x7e0) * a) & 0x7e000;
1580 result >>= 8;
1581 return swap16(result);
1583 #elif LCD_PIXELFORMAT == RGB565
1584 static inline unsigned fade_color(pix_t c, unsigned a)
1586 unsigned int result;
1587 a = (a + 2) & 0x1fc;
1588 result = ((c & 0xf81f) * a) & 0xf81f00;
1589 result |= ((c & 0x7e0) * a) & 0x7e000;
1590 result >>= 8;
1591 return result;
1593 #else
1594 static inline unsigned fade_color(pix_t c, unsigned a)
1596 unsigned val = c;
1597 return MULUQ(val, a) >> 8;
1599 #endif
1602 * Render a single slide
1603 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1604 * on the slide from its center, zo is the slide's depth offset from the plane
1605 * of the display, r is the angle at which the slide is tilted, and xp is the
1606 * point on the display corresponding to xs on the slide, the projection
1607 * formulas are:
1609 * z * (xc + xs * cos(r))
1610 * xp = ──────────────────────
1611 * z + zo + xs * sin(r)
1613 * z * (xc - xp) - xp * zo
1614 * xs = ────────────────────────
1615 * xp * sin(r) - z * cos(r)
1617 * We use the xp projection once, to find the left edge of the slide on the
1618 * display. From there, we use the xs reverse projection to find the horizontal
1619 * offset from the slide center of each column on the screen, until we reach
1620 * the right edge of the slide, or the screen. The reverse projection can be
1621 * optimized by saving the numerator and denominator of the fraction, which can
1622 * then be incremented by (z + zo) and sin(r) respectively.
1624 void render_slide(struct slide_data *slide, const int alpha)
1626 struct dim *bmp = surface(slide->slide_index);
1627 if (!bmp) {
1628 return;
1630 if (slide->angle > 255 || slide->angle < -255)
1631 return;
1632 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1634 const int sw = bmp->width;
1635 const int sh = bmp->height;
1636 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1637 const int w = LCD_WIDTH;
1639 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1641 if (alpha == 256) { /* opaque -> copy table */
1642 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1643 } else { /* precalculate faded table */
1644 int i, lalpha;
1645 for (i = 0; i < REFLECT_HEIGHT; i++) {
1646 lalpha = reflect_table[i];
1647 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1651 PFreal cosr = fcos(slide->angle);
1652 PFreal sinr = fsin(slide->angle);
1653 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1654 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1655 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1656 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1657 (CAM_DIST_R + zo + fmul(xs,sinr)));
1659 /* Since we're finding the screen position of the left edge of the slide,
1660 * we round up.
1662 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1663 >> PFREAL_SHIFT;
1664 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1665 if (xi >= w) {
1666 return;
1668 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1669 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1670 xs = fdiv(xsnum, xsden);
1672 xsnumi = -CAM_DIST_R - zo;
1673 xsdeni = sinr;
1674 int x;
1675 int dy = PFREAL_ONE;
1676 for (x = xi; x < w; x++) {
1677 int column = (xs - slide_left) / PFREAL_ONE;
1678 if (column >= sw)
1679 break;
1680 if (zo || slide->angle)
1681 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1683 const pix_t *ptr = &src[column * bmp->height];
1684 const int pixelstep = BUFFER_WIDTH;
1686 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1687 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1688 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1690 if (alpha == 256) {
1691 while (p >= plim) {
1692 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1693 p -= dy;
1694 pixel -= pixelstep;
1696 } else {
1697 while (p >= plim) {
1698 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1699 p -= dy;
1700 pixel -= pixelstep;
1703 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1704 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1705 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1706 p + (LCD_HEIGHT/2) * dy);
1707 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1709 if (alpha == 256) {
1710 while (p < plim) {
1711 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1712 p += dy;
1713 pixel += pixelstep;
1715 } else {
1716 while (p < plim) {
1717 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1718 p += dy;
1719 pixel += pixelstep;
1722 while (p < plim2) {
1723 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1724 int lalpha = reftab[ty];
1725 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1726 p += dy;
1727 pixel += pixelstep;
1730 if (zo || slide->angle)
1732 xsnum += xsnumi;
1733 xsden += xsdeni;
1734 xs = fdiv(xsnum, xsden);
1735 } else
1736 xs += PFREAL_ONE;
1739 /* let the music play... */
1740 rb->yield();
1741 return;
1746 Jump the the given slide_index
1748 static inline void set_current_slide(const int slide_index)
1750 int old_center_index = center_index;
1751 step = 0;
1752 center_index = fbound(slide_index, 0, number_of_slides - 1);
1753 if (old_center_index != center_index)
1754 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1755 target = center_index;
1756 slide_frame = slide_index << 16;
1757 reset_slides();
1761 Start the animation for changing slides
1763 void start_animation(void)
1765 step = (target < center_slide.slide_index) ? -1 : 1;
1766 pf_state = pf_scrolling;
1770 Go to the previous slide
1772 void show_previous_slide(void)
1774 if (step == 0) {
1775 if (center_index > 0) {
1776 target = center_index - 1;
1777 start_animation();
1779 } else if ( step > 0 ) {
1780 target = center_index;
1781 start_animation();
1782 } else {
1783 target = fmax(0, center_index - 2);
1789 Go to the next slide
1791 void show_next_slide(void)
1793 if (step == 0) {
1794 if (center_index < number_of_slides - 1) {
1795 target = center_index + 1;
1796 start_animation();
1798 } else if ( step < 0 ) {
1799 target = center_index;
1800 start_animation();
1801 } else {
1802 target = fmin(center_index + 2, number_of_slides - 1);
1808 Render the slides. Updates only the offscreen buffer.
1810 void render_all_slides(void)
1812 MYLCD(set_background)(G_BRIGHT(0));
1813 /* TODO: Optimizes this by e.g. invalidating rects */
1814 MYLCD(clear_display)();
1816 int nleft = num_slides;
1817 int nright = num_slides;
1819 int index;
1820 if (step == 0) {
1821 /* no animation, boring plain rendering */
1822 for (index = nleft - 2; index >= 0; index--) {
1823 int alpha = (index < nleft - 2) ? 256 : 128;
1824 alpha -= extra_fade;
1825 if (alpha > 0 )
1826 render_slide(&left_slides[index], alpha);
1828 for (index = nright - 2; index >= 0; index--) {
1829 int alpha = (index < nright - 2) ? 256 : 128;
1830 alpha -= extra_fade;
1831 if (alpha > 0 )
1832 render_slide(&right_slides[index], alpha);
1834 } else {
1835 /* the first and last slide must fade in/fade out */
1836 for (index = nleft - 1; index >= 0; index--) {
1837 int alpha = 256;
1838 if (index == nleft - 1)
1839 alpha = (step > 0) ? 0 : 128 - fade / 2;
1840 if (index == nleft - 2)
1841 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1842 if (index == nleft - 3)
1843 alpha = (step > 0) ? 256 - fade / 2 : 256;
1844 render_slide(&left_slides[index], alpha);
1846 for (index = nright - 1; index >= 0; index--) {
1847 int alpha = (index < nright - 2) ? 256 : 128;
1848 if (index == nright - 1)
1849 alpha = (step > 0) ? fade / 2 : 0;
1850 if (index == nright - 2)
1851 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1852 if (index == nright - 3)
1853 alpha = (step > 0) ? 256 : 128 + fade / 2;
1854 render_slide(&right_slides[index], alpha);
1857 render_slide(&center_slide, 256);
1862 Updates the animation effect. Call this periodically from a timer.
1864 void update_scroll_animation(void)
1866 if (step == 0)
1867 return;
1869 int speed = 16384;
1870 int i;
1872 /* deaccelerate when approaching the target */
1873 if (true) {
1874 const int max = 2 * 65536;
1876 int fi = slide_frame;
1877 fi -= (target << 16);
1878 if (fi < 0)
1879 fi = -fi;
1880 fi = fmin(fi, max);
1882 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1883 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1886 slide_frame += speed * step;
1888 int index = slide_frame >> 16;
1889 int pos = slide_frame & 0xffff;
1890 int neg = 65536 - pos;
1891 int tick = (step < 0) ? neg : pos;
1892 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1894 /* the leftmost and rightmost slide must fade away */
1895 fade = pos / 256;
1897 if (step < 0)
1898 index++;
1899 if (center_index != index) {
1900 center_index = index;
1901 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1902 slide_frame = index << 16;
1903 center_slide.slide_index = center_index;
1904 for (i = 0; i < num_slides; i++)
1905 left_slides[i].slide_index = center_index - 1 - i;
1906 for (i = 0; i < num_slides; i++)
1907 right_slides[i].slide_index = center_index + 1 + i;
1910 center_slide.angle = (step * tick * itilt) >> 16;
1911 center_slide.cx = -step * fmul(offsetX, ftick);
1912 center_slide.cy = fmul(offsetY, ftick);
1914 if (center_index == target) {
1915 reset_slides();
1916 pf_state = pf_idle;
1917 step = 0;
1918 fade = 256;
1919 return;
1922 for (i = 0; i < num_slides; i++) {
1923 struct slide_data *si = &left_slides[i];
1924 si->angle = itilt;
1925 si->cx =
1926 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1927 * slide_spacing * ftick);
1928 si->cy = offsetY;
1931 for (i = 0; i < num_slides; i++) {
1932 struct slide_data *si = &right_slides[i];
1933 si->angle = -itilt;
1934 si->cx =
1935 offsetX + slide_spacing * i * PFREAL_ONE - step
1936 * slide_spacing * ftick;
1937 si->cy = offsetY;
1940 if (step > 0) {
1941 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1942 right_slides[0].angle = -(neg * itilt) >> 16;
1943 right_slides[0].cx = fmul(offsetX, ftick);
1944 right_slides[0].cy = fmul(offsetY, ftick);
1945 } else {
1946 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1947 left_slides[0].angle = (pos * itilt) >> 16;
1948 left_slides[0].cx = -fmul(offsetX, ftick);
1949 left_slides[0].cy = fmul(offsetY, ftick);
1952 /* must change direction ? */
1953 if (target < index)
1954 if (step > 0)
1955 step = -1;
1956 if (target > index)
1957 if (step < 0)
1958 step = 1;
1963 Cleanup the plugin
1965 void cleanup(void *parameter)
1967 (void) parameter;
1968 /* Turn on backlight timeout (revert to settings) */
1969 backlight_use_settings(); /* backlight control in lib/helper.c */
1971 #ifdef USEGSLIB
1972 grey_release();
1973 #endif
1977 Create the "?" slide, that is shown while loading
1978 or when no cover was found.
1980 int create_empty_slide(bool force)
1982 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1983 struct bitmap input_bmp;
1984 int ret;
1985 input_bmp.width = DISPLAY_WIDTH;
1986 input_bmp.height = DISPLAY_HEIGHT;
1987 #if LCD_DEPTH > 1
1988 input_bmp.format = FORMAT_NATIVE;
1989 #endif
1990 input_bmp.data = (char*)buf;
1991 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1992 buf_size,
1993 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1994 &format_transposed);
1995 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1996 return false;
1999 return true;
2003 Shows the album name setting menu
2005 int album_name_menu(void)
2007 int selection = show_album_name;
2009 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2010 "Hide album title", "Show at the bottom", "Show at the top");
2011 rb->do_menu(&album_name_menu, &selection, NULL, false);
2013 show_album_name = selection;
2014 return GO_TO_PREVIOUS;
2018 Shows the settings menu
2020 int settings_menu(void)
2022 int selection = 0;
2023 bool old_val;
2025 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2026 "Spacing", "Centre margin", "Number of slides", "Zoom",
2027 "Show album title", "Resize Covers", "Rebuild cache");
2029 do {
2030 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2031 switch(selection) {
2032 case 0:
2033 rb->set_bool("Show FPS", &show_fps);
2034 reset_track_list();
2035 break;
2037 case 1:
2038 rb->set_int("Spacing between slides", "", 1,
2039 &slide_spacing,
2040 NULL, 1, 0, 100, NULL );
2041 recalc_offsets();
2042 reset_slides();
2043 break;
2045 case 2:
2046 rb->set_int("Centre margin", "", 1,
2047 &center_margin,
2048 NULL, 1, 0, 80, NULL );
2049 recalc_offsets();
2050 reset_slides();
2051 break;
2053 case 3:
2054 rb->set_int("Number of slides", "", 1, &num_slides,
2055 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2056 recalc_offsets();
2057 reset_slides();
2058 break;
2060 case 4:
2061 rb->set_int("Zoom", "", 1, &zoom,
2062 NULL, 1, 10, 300, NULL );
2063 recalc_offsets();
2064 reset_slides();
2065 break;
2066 case 5:
2067 album_name_menu();
2068 reset_track_list();
2069 recalc_offsets();
2070 reset_slides();
2071 break;
2072 case 6:
2073 old_val = resize;
2074 rb->set_bool("Resize Covers", &resize);
2075 if (old_val == resize) /* changed? */
2076 break;
2077 /* fallthrough if changed, since cache needs to be rebuilt */
2078 case 7:
2079 cache_version = 0;
2080 rb->remove(EMPTY_SLIDE);
2081 rb->splash(HZ, "Cache will be rebuilt on next restart");
2082 break;
2084 case MENU_ATTACHED_USB:
2085 return PLUGIN_USB_CONNECTED;
2087 } while ( selection >= 0 );
2088 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2089 return 0;
2093 Show the main menu
2095 int main_menu(void)
2097 int selection = 0;
2098 int result;
2100 #if LCD_DEPTH > 1
2101 rb->lcd_set_foreground(N_BRIGHT(255));
2102 #endif
2104 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2105 "Settings", "Return", "Quit");
2106 while (1) {
2107 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2108 case 0:
2109 result = settings_menu();
2110 if ( result != 0 ) return result;
2111 break;
2113 case 1:
2114 return 0;
2116 case 2:
2117 return -1;
2119 case MENU_ATTACHED_USB:
2120 return PLUGIN_USB_CONNECTED;
2122 default:
2123 return 0;
2129 Animation step for zooming into the current cover
2131 void update_cover_in_animation(void)
2133 cover_animation_keyframe++;
2134 if( cover_animation_keyframe < 20 ) {
2135 center_slide.distance-=5;
2136 center_slide.angle+=1;
2137 extra_fade += 13;
2139 else if( cover_animation_keyframe < 35 ) {
2140 center_slide.angle+=16;
2142 else {
2143 cover_animation_keyframe = 0;
2144 pf_state = pf_show_tracks;
2149 Animation step for zooming out the current cover
2151 void update_cover_out_animation(void)
2153 cover_animation_keyframe++;
2154 if( cover_animation_keyframe <= 15 ) {
2155 center_slide.angle-=16;
2157 else if( cover_animation_keyframe < 35 ) {
2158 center_slide.distance+=5;
2159 center_slide.angle-=1;
2160 extra_fade -= 13;
2162 else {
2163 cover_animation_keyframe = 0;
2164 pf_state = pf_idle;
2169 Draw a blue gradient at y with height h
2171 static inline void draw_gradient(int y, int h)
2173 static int r, inc, c;
2174 inc = (100 << 8) / h;
2175 c = 0;
2176 selected_track_pulse = (selected_track_pulse+1) % 10;
2177 int c2 = selected_track_pulse - 5;
2178 for (r=0; r<h; r++) {
2179 #ifdef HAVE_LCD_COLOR
2180 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2181 c2+250-(c >> 8)));
2182 #else
2183 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2184 #endif
2185 MYLCD(hline)(0, LCD_WIDTH, r+y);
2186 if ( r > h/2 )
2187 c-=inc;
2188 else
2189 c+=inc;
2194 static void track_list_yh(int char_height)
2196 switch (show_album_name)
2198 case album_name_hide:
2199 track_list_y = (show_fps ? char_height : 0);
2200 track_list_h = LCD_HEIGHT - track_list_y;
2201 break;
2202 case album_name_bottom:
2203 track_list_y = (show_fps ? char_height : 0);
2204 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2205 break;
2206 default: /* case album_name_top */
2207 track_list_y = char_height * 2;
2208 track_list_h = LCD_HEIGHT - track_list_y -
2209 (show_fps ? char_height : 0);
2210 break;
2215 Reset the track list after a album change
2217 void reset_track_list(void)
2219 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2220 track_list_yh(albumtxt_h);
2221 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2222 start_index_track_list = 0;
2223 track_scroll_index = 0;
2224 track_scroll_dir = 1;
2225 selected_track = 0;
2227 /* let the tracklist start more centered
2228 * if the screen isn't filled with tracks */
2229 if (track_count*albumtxt_h < track_list_h)
2231 track_list_h = track_count * albumtxt_h;
2232 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2237 Display the list of tracks
2239 void show_track_list(void)
2241 MYLCD(clear_display)();
2242 if ( center_slide.slide_index != track_index ) {
2243 create_track_index(center_slide.slide_index);
2244 reset_track_list();
2246 static int titletxt_w, titletxt_x, color, titletxt_h;
2247 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2249 int titletxt_y = track_list_y;
2250 int track_i;
2251 track_i = start_index_track_list;
2252 for (;track_i < track_list_visible_entries+start_index_track_list;
2253 track_i++)
2255 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2256 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2257 if ( track_i == selected_track ) {
2258 draw_gradient(titletxt_y, titletxt_h);
2259 MYLCD(set_foreground)(G_BRIGHT(255));
2260 if (titletxt_w > LCD_WIDTH ) {
2261 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2262 track_scroll_dir = 1;
2263 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2264 track_scroll_index += track_scroll_dir*2;
2265 titletxt_x = track_scroll_index;
2267 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2269 else {
2270 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2271 MYLCD(set_foreground)(G_BRIGHT(color));
2272 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2274 titletxt_y += titletxt_h;
2278 void select_next_track(void)
2280 if ( selected_track < track_count - 1 ) {
2281 selected_track++;
2282 track_scroll_index = 0;
2283 track_scroll_dir = 1;
2284 if (selected_track==(track_list_visible_entries+start_index_track_list))
2285 start_index_track_list++;
2289 void select_prev_track(void)
2291 if (selected_track > 0 ) {
2292 if (selected_track==start_index_track_list) start_index_track_list--;
2293 track_scroll_index = 0;
2294 track_scroll_dir = 1;
2295 selected_track--;
2300 Draw the current album name
2302 void draw_album_text(void)
2304 if (0 == show_album_name)
2305 return;
2307 int albumtxt_w, albumtxt_h;
2308 int albumtxt_y = 0;
2310 char *albumtxt;
2311 int c;
2312 /* Draw album text */
2313 if ( pf_state == pf_scrolling ) {
2314 c = ((slide_frame & 0xffff )/ 255);
2315 if (step < 0) c = 255-c;
2316 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2317 albumtxt = get_album_name(center_index+step);
2318 c = (c-128)*2;
2320 else {
2321 albumtxt = get_album_name(center_index);
2322 c = (128-c)*2;
2325 else {
2326 c= 255;
2327 albumtxt = get_album_name(center_index);
2330 MYLCD(set_foreground)(G_BRIGHT(c));
2331 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2332 if (center_index != prev_center_index) {
2333 albumtxt_x = 0;
2334 albumtxt_dir = -1;
2335 prev_center_index = center_index;
2338 if (show_album_name == album_name_top)
2339 albumtxt_y = albumtxt_h / 2;
2340 else
2341 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2343 if (albumtxt_w > LCD_WIDTH ) {
2344 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2345 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2346 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2347 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2348 albumtxt_x += albumtxt_dir;
2351 else {
2352 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2360 Main function that also contain the main plasma
2361 algorithm.
2363 int main(void)
2365 int ret;
2367 rb->lcd_setfont(FONT_UI);
2368 draw_splashscreen();
2370 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2371 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2372 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2373 return PLUGIN_ERROR;
2377 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2379 init_reflect_table();
2381 ALIGN_BUFFER(buf, buf_size, 4);
2382 ret = create_album_index();
2383 if (ret == ERROR_BUFFER_FULL) {
2384 rb->splash(HZ, "Not enough memory for album names");
2385 return PLUGIN_ERROR;
2386 } else if (ret == ERROR_NO_ALBUMS) {
2387 rb->splash(HZ, "No albums found. Please enable database");
2388 return PLUGIN_ERROR;
2391 ALIGN_BUFFER(buf, buf_size, 4);
2392 number_of_slides = album_count;
2393 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2394 rb->splash(HZ, "Could not create album art cache");
2395 return PLUGIN_ERROR;
2398 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2399 rb->splash(HZ, "Could not load the empty slide");
2400 return PLUGIN_ERROR;
2402 cache_version = CACHE_VERSION;
2403 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2406 #ifdef USEGSLIB
2407 long grey_buf_used;
2408 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2409 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2411 rb->splash(HZ, "Greylib init failed!");
2412 return PLUGIN_ERROR;
2414 grey_setfont(FONT_UI);
2415 buf_size -= grey_buf_used;
2416 buf = (void*)(grey_buf_used + (char*)buf);
2417 #endif
2418 buflib_init(&buf_ctx, (void *)buf, buf_size);
2420 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2422 rb->splash(HZ, "Unable to load empty slide image");
2423 return PLUGIN_ERROR;
2426 if (!create_pf_thread()) {
2427 rb->splash(HZ, "Cannot create thread!");
2428 return PLUGIN_ERROR;
2431 int i;
2433 /* initialize */
2434 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2435 cache[i].hid = 0;
2436 cache[i].index = 0;
2437 cache[i].next = i + 1;
2438 cache[i].prev = i - 1;
2440 cache[0].prev = i - 1;
2441 cache[i - 1].next = 0;
2442 cache_free = 0;
2443 buffer = LCD_BUF;
2445 pf_state = pf_idle;
2447 track_index = -1;
2448 extra_fade = 0;
2449 slide_frame = 0;
2450 step = 0;
2451 target = 0;
2452 fade = 256;
2454 recalc_offsets();
2455 reset_slides();
2457 char fpstxt[10];
2458 int button;
2460 int frames = 0;
2461 long last_update = *rb->current_tick;
2462 long current_update;
2463 long update_interval = 100;
2464 int fps = 0;
2465 int fpstxt_y;
2467 bool instant_update;
2468 #ifdef USEGSLIB
2469 grey_show(true);
2470 grey_set_drawmode(DRMODE_FG);
2471 #endif
2472 rb->lcd_set_drawmode(DRMODE_FG);
2473 while (true) {
2474 current_update = *rb->current_tick;
2475 frames++;
2477 /* Initial rendering */
2478 instant_update = false;
2480 /* Handle states */
2481 switch ( pf_state ) {
2482 case pf_scrolling:
2483 update_scroll_animation();
2484 render_all_slides();
2485 instant_update = true;
2486 break;
2487 case pf_cover_in:
2488 update_cover_in_animation();
2489 render_all_slides();
2490 instant_update = true;
2491 break;
2492 case pf_cover_out:
2493 update_cover_out_animation();
2494 render_all_slides();
2495 instant_update = true;
2496 break;
2497 case pf_show_tracks:
2498 show_track_list();
2499 break;
2500 case pf_idle:
2501 render_all_slides();
2502 break;
2505 /* Calculate FPS */
2506 if (current_update - last_update > update_interval) {
2507 fps = frames * HZ / (current_update - last_update);
2508 last_update = current_update;
2509 frames = 0;
2511 /* Draw FPS */
2512 if (show_fps)
2514 #ifdef USEGSLIB
2515 MYLCD(set_foreground)(G_BRIGHT(255));
2516 #else
2517 MYLCD(set_foreground)(G_PIX(255,0,0));
2518 #endif
2519 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2520 if (show_album_name == album_name_top)
2521 fpstxt_y = LCD_HEIGHT -
2522 rb->screens[SCREEN_MAIN]->getcharheight();
2523 else
2524 fpstxt_y = 0;
2525 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2527 draw_album_text();
2530 /* Copy offscreen buffer to LCD and give time to other threads */
2531 MYLCD(update)();
2532 rb->yield();
2534 /*/ Handle buttons */
2535 button = rb->get_custom_action(CONTEXT_CUSTOM|
2536 (pf_state == pf_show_tracks ? 1 : 0),
2537 instant_update ? 0 : HZ/16,
2538 get_context_map);
2540 switch (button) {
2541 case PF_QUIT:
2542 return PLUGIN_OK;
2544 case PF_BACK:
2545 if ( pf_state == pf_show_tracks )
2547 buflib_buffer_in(&buf_ctx, borrowed);
2548 borrowed = 0;
2549 track_index = -1;
2550 pf_state = pf_cover_out;
2552 if (pf_state == pf_idle || pf_state == pf_scrolling)
2553 return PLUGIN_OK;
2554 break;
2556 case PF_MENU:
2557 #ifdef USEGSLIB
2558 grey_show(false);
2559 #endif
2560 ret = main_menu();
2561 if ( ret == -1 ) return PLUGIN_OK;
2562 if ( ret != 0 ) return i;
2563 #ifdef USEGSLIB
2564 grey_show(true);
2565 #endif
2566 MYLCD(set_drawmode)(DRMODE_FG);
2567 break;
2569 case PF_NEXT:
2570 case PF_NEXT_REPEAT:
2571 if ( pf_state == pf_show_tracks )
2572 select_next_track();
2573 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2574 show_next_slide();
2575 break;
2577 case PF_PREV:
2578 case PF_PREV_REPEAT:
2579 if ( pf_state == pf_show_tracks )
2580 select_prev_track();
2581 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2582 show_previous_slide();
2583 break;
2585 case PF_SELECT:
2586 if ( pf_state == pf_idle ) {
2587 pf_state = pf_cover_in;
2589 break;
2591 default:
2592 if (rb->default_event_handler_ex(button, cleanup, NULL)
2593 == SYS_USB_CONNECTED)
2594 return PLUGIN_USB_CONNECTED;
2595 break;
2602 /*************************** Plugin entry point ****************************/
2604 enum plugin_status plugin_start(const void *parameter)
2606 int ret;
2607 (void) parameter;
2608 #if LCD_DEPTH > 1
2609 rb->lcd_set_backdrop(NULL);
2610 #endif
2611 /* Turn off backlight timeout */
2612 backlight_force_on(); /* backlight control in lib/helper.c */
2613 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2614 rb->cpu_boost(true);
2615 #endif
2616 #if PLUGIN_BUFFER_SIZE > 0x10000
2617 buf = rb->plugin_get_buffer(&buf_size);
2618 #else
2619 buf = rb->plugin_get_audio_buffer(&buf_size);
2620 #endif
2621 ret = main();
2622 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2623 rb->cpu_boost(false);
2624 #endif
2625 if ( ret == PLUGIN_OK ) {
2626 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2627 CONFIG_VERSION))
2629 rb->splash(HZ, "Error writing config.");
2630 ret = PLUGIN_ERROR;
2634 end_pf_thread();
2635 cleanup(NULL);
2636 return ret;