Pictureflow: Don't show the playback control one targets that can't have playback...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobada47bca5ef420c1213e09588b7b0e222722fa78
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
26 #include "plugin.h"
27 #include <albumart.h>
28 #include "lib/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/helper.h"
31 #include "lib/configfile.h"
32 #include "lib/grey.h"
33 #include "lib/feature_wrappers.h"
34 #include "lib/buflib.h"
36 PLUGIN_HEADER
38 /******************************* Globals ***********************************/
41 * Targets which use plugin_get_audio_buffer() can't have playback from
42 * within pictureflow itself, as the whole core audio buffer is occupied */
43 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
45 #if PF_PLAYBACK_CAPABLE
46 #include "lib/playback_control.h"
47 #endif
49 #define PF_PREV ACTION_STD_PREV
50 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
51 #define PF_NEXT ACTION_STD_NEXT
52 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
53 #define PF_SELECT ACTION_STD_OK
54 #define PF_CONTEXT ACTION_STD_CONTEXT
55 #define PF_BACK ACTION_STD_CANCEL
56 #define PF_MENU ACTION_STD_MENU
57 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
59 #if !defined(HAVE_SCROLLWHEEL)
60 /* scrollwheel targets use the wheel, just as they do in lists,
61 * so there's no need for a special context,
62 * others use left/right here too (as oppsed to up/down in lists) */
63 const struct button_mapping pf_context_album_scroll[] =
65 #ifdef HAVE_TOUCHSCREEN
66 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
67 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
68 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
69 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
70 #endif
71 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
72 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
73 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
74 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
75 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
76 #else
77 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
78 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
79 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
80 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
81 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
82 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
83 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
84 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
85 #endif
86 #if CONFIG_KEYPAD == ONDIO_PAD
87 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
88 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
89 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
90 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
91 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
92 #endif
93 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
95 #endif /* !defined(HAVE_SCROLLWHEEL) */
97 const struct button_mapping pf_context_buttons[] =
99 #ifdef HAVE_TOUCHSCREEN
100 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
101 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
102 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
103 #endif
104 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
105 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
106 #elif CONFIG_KEYPAD == SANSA_C100_PAD
107 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
108 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
109 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
110 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
111 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
112 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
113 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
114 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
115 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
116 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
118 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
119 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
120 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
121 #if CONFIG_KEYPAD == COWOND2_PAD
122 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
123 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
124 #endif
125 #elif CONFIG_KEYPAD == SANSA_E200_PAD
126 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
127 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
128 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
129 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
130 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
131 || (CONFIG_KEYPAD == IPOD_4G_PAD)
132 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
133 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
134 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
135 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
136 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
137 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
138 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
139 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
140 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
141 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
142 #endif
143 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
144 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
145 #else
146 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
147 #endif
149 const struct button_mapping *pf_contexts[] =
151 #if !defined(HAVE_SCROLLWHEEL)
152 pf_context_album_scroll,
153 #endif
154 pf_context_buttons
157 #if LCD_DEPTH < 8
158 #if LCD_DEPTH > 1
159 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
160 #else /* LCD_DEPTH <= 1 */
161 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
162 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
163 #define PICTUREFLOW_DRMODE DRMODE_SOLID
164 #else
165 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
166 #endif
167 #endif /* LCD_DEPTH <= 1 */
168 #define USEGSLIB
169 GREY_INFO_STRUCT
170 #define LCD_BUF _grey_info.buffer
171 #define MYLCD(fn) grey_ ## fn
172 #define G_PIX(r,g,b) \
173 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
174 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
175 #define G_BRIGHT(y) (y)
176 #define BUFFER_WIDTH _grey_info.width
177 #define BUFFER_HEIGHT _grey_info.height
178 typedef unsigned char pix_t;
179 #else /* LCD_DEPTH >= 8 */
180 #define LCD_BUF rb->lcd_framebuffer
181 #define MYLCD(fn) rb->lcd_ ## fn
182 #define G_PIX LCD_RGBPACK
183 #define N_PIX LCD_RGBPACK
184 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
185 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
186 #define BUFFER_WIDTH LCD_WIDTH
187 #define BUFFER_HEIGHT LCD_HEIGHT
188 typedef fb_data pix_t;
189 #endif /* LCD_DEPTH >= 8 */
191 /* for fixed-point arithmetic, we need minimum 32-bit long
192 long long (64-bit) might be useful for multiplication and division */
193 #define PFreal long
194 #define PFREAL_SHIFT 10
195 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
196 #define PFREAL_ONE (1 << PFREAL_SHIFT)
197 #define PFREAL_HALF (PFREAL_ONE >> 1)
200 #define IANGLE_MAX 1024
201 #define IANGLE_MASK 1023
203 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
204 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
205 #define DISPLAY_HEIGHT REFLECT_TOP
206 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
207 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
208 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
209 (REFLECT_HEIGHT * 5))
210 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
211 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
212 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
213 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
214 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
216 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
218 #define MAX_SLIDES_COUNT 10
220 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
221 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
223 #define EV_EXIT 9999
224 #define EV_WAKEUP 1337
226 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
227 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
228 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
230 /* Error return values */
231 #define ERROR_NO_ALBUMS -1
232 #define ERROR_BUFFER_FULL -2
234 /* current version for cover cache */
235 #define CACHE_VERSION 3
236 #define CONFIG_VERSION 1
237 #define CONFIG_FILE "pictureflow.cfg"
239 /** structs we use */
241 struct slide_data {
242 int slide_index;
243 int angle;
244 PFreal cx;
245 PFreal cy;
246 PFreal distance;
249 struct slide_cache {
250 int index; /* index of the cached slide */
251 int hid; /* handle ID of the cached slide */
252 short next; /* "next" slide, with LRU last */
253 short prev; /* "previous" slide */
256 struct album_data {
257 int name_idx;
258 long seek;
261 struct track_data {
262 uint32_t sort;
263 int name_idx; /* offset to the track name */
264 long seek;
265 #if PF_PLAYBACK_CAPABLE
266 /* offset to the filename in the string, needed for playlist generation */
267 int filename_idx;
268 #endif
271 struct rect {
272 int left;
273 int right;
274 int top;
275 int bottom;
278 struct load_slide_event_data {
279 int slide_index;
280 int cache_index;
284 struct pfraw_header {
285 int32_t width; /* bmap width in pixels */
286 int32_t height; /* bmap height in pixels */
289 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
290 album_name_top };
291 static char* show_album_name_conf[] =
293 "hide",
294 "bottom",
295 "top"
298 #define MAX_SPACING 40
299 #define MAX_MARGIN 80
301 /* config values and their defaults */
302 static int slide_spacing = DISPLAY_WIDTH / 4;
303 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
304 static int num_slides = 4;
305 static int zoom = 100;
306 static bool show_fps = false;
307 static bool resize = true;
308 static int cache_version = 0;
309 static int show_album_name = (LCD_HEIGHT > 100)
310 ? album_name_top : album_name_bottom;
312 static struct configdata config[] =
314 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
315 NULL },
316 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
317 NULL },
318 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
319 NULL },
320 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
321 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
322 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
323 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
324 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
325 show_album_name_conf }
328 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
330 /** below we allocate the memory we want to use **/
332 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
333 static uint8_t reflect_table[REFLECT_HEIGHT];
334 static struct slide_data center_slide;
335 static struct slide_data left_slides[MAX_SLIDES_COUNT];
336 static struct slide_data right_slides[MAX_SLIDES_COUNT];
337 static int slide_frame;
338 static int step;
339 static int target;
340 static int fade;
341 static int center_index = 0; /* index of the slide that is in the center */
342 static int itilt;
343 static PFreal offsetX;
344 static PFreal offsetY;
345 static int number_of_slides;
347 static struct slide_cache cache[SLIDE_CACHE_SIZE];
348 static int cache_free;
349 static int cache_used = -1;
350 static int cache_left_index = -1;
351 static int cache_right_index = -1;
352 static int cache_center_index = -1;
354 /* use long for aligning */
355 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
356 /* queue (as array) for scheduling load_surface */
358 static int empty_slide_hid;
360 unsigned int thread_id;
361 struct event_queue thread_q;
363 static struct tagcache_search tcs;
365 static struct buflib_context buf_ctx;
367 static struct album_data *album;
368 static char *album_names;
369 static int album_count;
371 static struct track_data *tracks;
372 static char *track_names;
373 static size_t borrowed = 0;
374 static int track_count;
375 static int track_index;
376 static int selected_track;
377 static int selected_track_pulse;
378 void reset_track_list(void);
380 void * buf;
381 size_t buf_size;
383 static bool thread_is_running;
385 static int cover_animation_keyframe;
386 static int extra_fade;
388 static int albumtxt_x = 0;
389 static int albumtxt_dir = -1;
390 static int prev_center_index = -1;
392 static int start_index_track_list = 0;
393 static int track_list_visible_entries = 0;
394 static int track_list_y;
395 static int track_list_h;
396 static int track_scroll_index = 0;
397 static int track_scroll_dir = 1;
400 Proposals for transitions:
402 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
403 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
405 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
407 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
409 TODO:
410 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
411 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
413 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
415 enum pf_states {
416 pf_idle = 0,
417 pf_scrolling,
418 pf_cover_in,
419 pf_show_tracks,
420 pf_cover_out
423 static int pf_state;
425 /** code */
426 static bool free_slide_prio(int prio);
427 static inline unsigned fade_color(pix_t c, unsigned a);
428 bool save_pfraw(char* filename, struct bitmap *bm);
429 bool load_new_slide(void);
430 int load_surface(int);
432 static inline PFreal fmul(PFreal a, PFreal b)
434 return (a*b) >> PFREAL_SHIFT;
438 * This version preshifts each operand, which is useful when we know how many
439 * of the least significant bits will be empty, or are worried about overflow
440 * in a particular calculation
442 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
444 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
447 /* ARMv5+ has a clz instruction equivalent to our function.
449 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
450 static inline int clz(uint32_t v)
452 return __builtin_clz(v);
455 /* Otherwise, use our clz, which can be inlined */
456 #elif defined(CPU_COLDFIRE)
457 /* This clz is based on the log2(n) implementation at
458 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
459 * A clz benchmark plugin showed this to be about 14% faster on coldfire
460 * than the LUT-based version.
462 static inline int clz(uint32_t v)
464 int r = 32;
465 if (v >= 0x10000)
467 v >>= 16;
468 r -= 16;
470 if (v & 0xff00)
472 v >>= 8;
473 r -= 8;
475 if (v & 0xf0)
477 v >>= 4;
478 r -= 4;
480 if (v & 0xc)
482 v >>= 2;
483 r -= 2;
485 if (v & 2)
487 v >>= 1;
488 r -= 1;
490 r -= v;
491 return r;
493 #else
494 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
495 0, 0, 0, 0, 0, 0, 0, 0 };
496 /* This clz is based on the log2(n) implementation at
497 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
498 * It is not any faster than the one above, but trades 16B in the lookup table
499 * for a savings of 12B per each inlined call.
501 static inline int clz(uint32_t v)
503 int r = 28;
504 if (v >= 0x10000)
506 v >>= 16;
507 r -= 16;
509 if (v & 0xff00)
511 v >>= 8;
512 r -= 8;
514 if (v & 0xf0)
516 v >>= 4;
517 r -= 4;
519 return r + clz_lut[v];
521 #endif
523 /* Return the maximum possible left shift for a signed int32, without
524 * overflow
526 static inline int allowed_shift(int32_t val)
528 uint32_t uval = val ^ (val >> 31);
529 return clz(uval) - 1;
532 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
533 * num and den before dividing.
535 static inline PFreal fdiv(PFreal num, PFreal den)
537 int shift = allowed_shift(num);
538 shift = MIN(PFREAL_SHIFT, shift);
539 num <<= shift;
540 den >>= PFREAL_SHIFT - shift;
541 return num / den;
544 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
545 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
546 #define fabs(a) (a < 0 ? -a : a)
547 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
549 #if CONFIG_CPU == SH7034
550 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
551 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
552 #else
553 #define MULUQ(a, b) ((a) * (b))
554 #endif
557 #if 0
558 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
559 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
561 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
562 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
564 static inline PFreal fmul(PFreal a, PFreal b)
566 return (a*b) >> PFREAL_SHIFT;
569 static inline PFreal fdiv(PFreal n, PFreal m)
571 return (n<<(PFREAL_SHIFT))/m;
573 #endif
575 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
576 static const short sin_tab[] = {
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,
581 0, -100, -200, -297, -392, -483, -569, -650,
582 -724, -792, -851, -903, -946, -980, -1004, -1019,
583 -1024, -1019, -1004, -980, -946, -903, -851, -792,
584 -724, -650, -569, -483, -392, -297, -200, -100,
588 static inline PFreal fsin(int iangle)
590 iangle &= IANGLE_MASK;
592 int i = (iangle >> 4);
593 PFreal p = sin_tab[i];
594 PFreal q = sin_tab[(i+1)];
595 PFreal g = (q - p);
596 return p + g * (iangle-i*16)/16;
599 static inline PFreal fcos(int iangle)
601 return fsin(iangle + (IANGLE_MAX >> 2));
604 static inline unsigned scale_val(unsigned val, unsigned bits)
606 val = val * ((1 << bits) - 1);
607 return ((val >> 8) + val + 128) >> 8;
610 static void output_row_8_transposed(uint32_t row, void * row_in,
611 struct scaler_context *ctx)
613 pix_t *dest = (pix_t*)ctx->bm->data + row;
614 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
615 #ifdef USEGSLIB
616 uint8_t *qp = (uint8_t*)row_in;
617 for (; dest < end; dest += ctx->bm->height)
618 *dest = *qp++;
619 #else
620 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
621 unsigned r, g, b;
622 for (; dest < end; dest += ctx->bm->height)
624 r = scale_val(qp->red, 5);
625 g = scale_val(qp->green, 6);
626 b = scale_val((qp++)->blue, 5);
627 *dest = LCD_RGBPACK_LCD(r,g,b);
629 #endif
632 static void output_row_32_transposed(uint32_t row, void * row_in,
633 struct scaler_context *ctx)
635 pix_t *dest = (pix_t*)ctx->bm->data + row;
636 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
637 #ifdef USEGSLIB
638 uint32_t *qp = (uint32_t*)row_in;
639 for (; dest < end; dest += ctx->bm->height)
640 *dest = SC_OUT(*qp++, ctx);
641 #else
642 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
643 int r, g, b;
644 for (; dest < end; dest += ctx->bm->height)
646 r = scale_val(SC_OUT(qp->r, ctx), 5);
647 g = scale_val(SC_OUT(qp->g, ctx), 6);
648 b = scale_val(SC_OUT(qp->b, ctx), 5);
649 qp++;
650 *dest = LCD_RGBPACK_LCD(r,g,b);
652 #endif
655 #ifdef HAVE_LCD_COLOR
656 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
657 struct scaler_context *ctx)
659 pix_t *dest = (pix_t*)ctx->bm->data + row;
660 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
661 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
662 for (; dest < end; dest += ctx->bm->height)
664 unsigned r, g, b, y, u, v;
665 y = SC_OUT(qp->b, ctx);
666 u = SC_OUT(qp->g, ctx);
667 v = SC_OUT(qp->r, ctx);
668 qp++;
669 yuv_to_rgb(y, u, v, &r, &g, &b);
670 r = scale_val(r, 5);
671 g = scale_val(g, 6);
672 b = scale_val(b, 5);
673 *dest = LCD_RGBPACK_LCD(r, g, b);
676 #endif
678 static unsigned int get_size(struct bitmap *bm)
680 return bm->width * bm->height * sizeof(pix_t);
683 const struct custom_format format_transposed = {
684 .output_row_8 = output_row_8_transposed,
685 #ifdef HAVE_LCD_COLOR
686 .output_row_32 = {
687 output_row_32_transposed,
688 output_row_32_transposed_fromyuv
690 #else
691 .output_row_32 = output_row_32_transposed,
692 #endif
693 .get_size = get_size
696 static const struct button_mapping* get_context_map(int context)
698 return pf_contexts[context & ~CONTEXT_PLUGIN];
701 /* Create the lookup table with the scaling values for the reflections */
702 void init_reflect_table(void)
704 int i;
705 for (i = 0; i < REFLECT_HEIGHT; i++)
706 reflect_table[i] =
707 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
708 (5 * REFLECT_HEIGHT);
712 Create an index of all albums from the database.
713 Also store the album names so we can access them later.
715 int create_album_index(void)
717 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
718 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
719 album_count = 0;
720 rb->tagcache_search(&tcs, tag_album);
721 unsigned int l, old_l = 0;
722 album_names = buf;
723 album[0].name_idx = 0;
724 while (rb->tagcache_get_next(&tcs))
726 buf_size -= sizeof(struct album_data);
727 l = tcs.result_len;
728 if ( album_count > 0 )
729 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
731 if ( l > buf_size )
732 /* not enough memory */
733 return ERROR_BUFFER_FULL;
735 rb->strcpy(buf, tcs.result);
736 buf_size -= l;
737 buf = l + (char *)buf;
738 album[-album_count].seek = tcs.result_seek;
739 old_l = l;
740 album_count++;
742 rb->tagcache_search_finish(&tcs);
743 ALIGN_BUFFER(buf, buf_size, 4);
744 int i;
745 struct album_data* tmp_album = (struct album_data*)buf;
746 for (i = album_count - 1; i >= 0; i--)
747 tmp_album[i] = album[-i];
748 album = tmp_album;
749 buf = album + album_count;
750 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
754 Return a pointer to the album name of the given slide_index
756 char* get_album_name(const int slide_index)
758 return album_names + album[slide_index].name_idx;
762 Return a pointer to the track name of the active album
763 create_track_index has to be called first.
765 char* get_track_name(const int track_index)
767 if ( track_index < track_count )
768 return track_names + tracks[track_index].name_idx;
769 return 0;
771 #if PF_PLAYBACK_CAPABLE
772 char* get_track_filename(const int track_index)
774 if ( track_index < track_count )
775 return track_names + tracks[track_index].filename_idx;
776 return 0;
778 #endif
780 Compare two unsigned ints passed via pointers.
782 int compare_tracks (const void *a_v, const void *b_v)
784 uint32_t a = ((struct track_data *)a_v)->sort;
785 uint32_t b = ((struct track_data *)b_v)->sort;
786 return (int)(a - b);
790 Create the track index of the given slide_index.
792 void create_track_index(const int slide_index)
794 if ( slide_index == track_index )
795 return;
796 track_index = slide_index;
798 if (!rb->tagcache_search(&tcs, tag_title))
799 goto fail;
801 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
802 track_count=0;
803 int string_index = 0, track_num;
804 int disc_num;
805 size_t out = 0;
806 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
807 borrowed += out;
808 int avail = borrowed;
809 tracks = (struct track_data*)(track_names + borrowed);
810 while (rb->tagcache_get_next(&tcs))
812 int len = 0, fn_idx = 0;
814 avail -= sizeof(struct track_data);
815 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
816 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
818 if (disc_num < 0)
819 disc_num = 0;
820 retry:
821 if (track_num >= 0)
823 if (disc_num)
824 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
825 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
826 else
827 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
828 "%d: %s", track_num + 1, tcs.result);
830 else
832 track_num = 0;
833 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
834 "%s", tcs.result);
836 if (fn_idx <= 0)
837 goto fail;
838 #if PF_PLAYBACK_CAPABLE
839 int remain = avail - fn_idx;
840 if (remain >= MAX_PATH)
841 { /* retrieve filename for building the playlist */
842 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
843 track_names + string_index + fn_idx, remain);
844 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
845 /* make sure track name and file name are really split by a \0, else
846 * get_track_name might fail */
847 *(track_names + string_index + fn_idx -1) = '\0';
850 else /* request more buffer so that track and filename fit */
851 len = (avail - remain) + MAX_PATH;
852 #else
853 len = fn_idx;
854 #endif
855 if (len > avail)
857 while (len > avail)
859 if (!free_slide_prio(0))
860 goto fail;
861 out = 0;
862 buflib_buffer_out(&buf_ctx, &out);
863 avail += out;
864 borrowed += out;
865 if (track_count)
867 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
868 unsigned int bytes = track_count * sizeof(struct track_data);
869 rb->memmove(new_tracks, tracks, bytes);
870 tracks = new_tracks;
873 goto retry;
876 avail -= len;
877 tracks--;
878 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
879 tracks->name_idx = string_index;
880 tracks->seek = tcs.result_seek;
881 #if PF_PLAYBACK_CAPABLE
882 tracks->filename_idx = fn_idx + string_index;
883 #endif
884 track_count++;
885 string_index += len;
888 rb->tagcache_search_finish(&tcs);
890 /* now fix the track list order */
891 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
892 return;
893 fail:
894 track_count = 0;
895 return;
899 Determine filename of the album art for the given slide_index and
900 store the result in buf.
901 The algorithm looks for the first track of the given album uses
902 find_albumart to find the filename.
904 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
905 int buflen)
907 if ( slide_index == -1 )
909 rb->strncpy( buf, EMPTY_SLIDE, buflen );
912 if (!rb->tagcache_search(&tcs, tag_filename))
913 return false;
915 bool result;
916 /* find the first track of the album */
917 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
919 if ( rb->tagcache_get_next(&tcs) ) {
920 struct mp3entry id3;
921 int fd;
923 #ifdef HAVE_TC_RAMCACHE
924 if (rb->tagcache_fill_tags(&id3, tcs.result))
926 rb->strncpy(id3.path, tcs.result, sizeof(id3.path));
927 id3.path[sizeof(id3.path) - 1] = 0;
929 else
930 #endif
932 fd = rb->open(tcs.result, O_RDONLY);
933 rb->get_metadata(&id3, fd, tcs.result);
934 rb->close(fd);
936 if ( search_albumart_files(&id3, ":", buf, buflen) )
937 result = true;
938 else
939 result = false;
941 else {
942 /* did not find a matching track */
943 result = false;
945 rb->tagcache_search_finish(&tcs);
946 return result;
950 Draw the PictureFlow logo
952 void draw_splashscreen(void)
954 unsigned char * buf_tmp = buf;
955 size_t buf_tmp_size = buf_size;
956 struct screen* display = rb->screens[0];
957 #if FB_DATA_SZ > 1
958 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
959 #endif
960 struct bitmap logo = {
961 #if LCD_WIDTH < 200
962 .width = 100,
963 .height = 18,
964 #else
965 .width = 193,
966 .height = 34,
967 #endif
968 .data = buf_tmp
970 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
971 NULL);
972 #if LCD_DEPTH > 1
973 rb->lcd_set_background(N_BRIGHT(0));
974 rb->lcd_set_foreground(N_BRIGHT(255));
975 #else
976 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
977 #endif
978 rb->lcd_clear_display();
980 if (ret > 0)
982 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
983 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
984 #endif
985 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
986 logo.width, logo.height);
987 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
988 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
989 #endif
992 rb->lcd_update();
997 Draw a simple progress bar
999 void draw_progressbar(int step)
1001 int txt_w, txt_h;
1002 const int bar_height = 22;
1003 const int w = LCD_WIDTH - 20;
1004 const int x = 10;
1006 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1008 int y = (LCD_HEIGHT - txt_h)/2;
1010 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1011 y += (txt_h + 5);
1013 #if LCD_DEPTH > 1
1014 rb->lcd_set_foreground(N_BRIGHT(100));
1015 #endif
1016 rb->lcd_drawrect(x, y, w+2, bar_height);
1017 #if LCD_DEPTH > 1
1018 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1019 #endif
1021 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1022 #if LCD_DEPTH > 1
1023 rb->lcd_set_foreground(N_BRIGHT(255));
1024 #endif
1025 rb->lcd_update();
1026 rb->yield();
1030 Precomupte the album art images and store them in CACHE_PREFIX.
1032 bool create_albumart_cache(void)
1034 int ret;
1036 int i, slides = 0;
1037 struct bitmap input_bmp;
1039 char pfraw_file[MAX_PATH];
1040 char albumart_file[MAX_PATH];
1041 unsigned int format = FORMAT_NATIVE;
1042 cache_version = 0;
1043 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1044 if (resize)
1045 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1046 for (i=0; i < album_count; i++)
1048 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1050 /* delete existing cache, so it's a true rebuild */
1051 if(rb->file_exists(pfraw_file))
1052 rb->remove(pfraw_file);
1053 draw_progressbar(i);
1054 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1055 continue;
1057 input_bmp.data = buf;
1058 input_bmp.width = DISPLAY_WIDTH;
1059 input_bmp.height = DISPLAY_HEIGHT;
1060 ret = read_image_file(albumart_file, &input_bmp,
1061 buf_size, format, &format_transposed);
1062 if (ret <= 0) {
1063 rb->splash(HZ, "Could not read bmp");
1064 continue; /* skip missing/broken files */
1066 if (!save_pfraw(pfraw_file, &input_bmp))
1068 rb->splash(HZ, "Could not write bmp");
1070 slides++;
1071 if ( rb->button_get(false) == PF_MENU ) return false;
1073 if ( slides == 0 ) {
1074 /* Warn the user that we couldn't find any albumart */
1075 rb->splash(2*HZ, "No album art found");
1076 return false;
1078 return true;
1082 Thread used for loading and preparing bitmaps in the background
1084 void thread(void)
1086 long sleep_time = 5 * HZ;
1087 struct queue_event ev;
1088 while (1) {
1089 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1090 switch (ev.id) {
1091 case EV_EXIT:
1092 return;
1093 case EV_WAKEUP:
1094 /* we just woke up */
1095 break;
1097 while ( load_new_slide() ) {
1098 rb->yield();
1099 switch (ev.id) {
1100 case EV_EXIT:
1101 return;
1109 End the thread by posting the EV_EXIT event
1111 void end_pf_thread(void)
1113 if ( thread_is_running ) {
1114 rb->queue_post(&thread_q, EV_EXIT, 0);
1115 rb->thread_wait(thread_id);
1116 /* remove the thread's queue from the broadcast list */
1117 rb->queue_delete(&thread_q);
1118 thread_is_running = false;
1125 Create the thread an setup the event queue
1127 bool create_pf_thread(void)
1129 /* put the thread's queue in the bcast list */
1130 rb->queue_init(&thread_q, true);
1131 if ((thread_id = rb->create_thread(
1132 thread,
1133 thread_stack,
1134 sizeof(thread_stack),
1136 "Picture load thread"
1137 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1138 PRIORITY_REALTIME + 1))
1139 IF_COP(, CPU)
1141 ) == 0) {
1142 return false;
1144 thread_is_running = true;
1145 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1146 return true;
1150 Safe the given bitmap as filename in the pfraw format
1152 bool save_pfraw(char* filename, struct bitmap *bm)
1154 struct pfraw_header bmph;
1155 bmph.width = bm->width;
1156 bmph.height = bm->height;
1157 int fh = rb->creat( filename );
1158 if( fh < 0 ) return false;
1159 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1160 int y;
1161 for( y = 0; y < bm->height; y++ )
1163 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1164 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1166 rb->close( fh );
1167 return true;
1172 * The following functions implement the linked-list-in-array used to manage
1173 * the LRU cache of slides, and the list of free cache slots.
1176 #define seek_right_while(start, cond) \
1177 ({ \
1178 int ind_, next_ = (start); \
1179 do { \
1180 ind_ = next_; \
1181 next_ = cache[ind_].next; \
1182 } while (next_ != cache_used && (cond)); \
1183 ind_; \
1186 #define seek_left_while(start, cond) \
1187 ({ \
1188 int ind_, next_ = (start); \
1189 do { \
1190 ind_ = next_; \
1191 next_ = cache[ind_].prev; \
1192 } while (ind_ != cache_used && (cond)); \
1193 ind_; \
1197 Pop the given item from the linked list starting at *head, returning the next
1198 item, or -1 if the list is now empty.
1200 static inline int lla_pop_item (int *head, int i)
1202 int prev = cache[i].prev;
1203 int next = cache[i].next;
1204 if (i == next)
1206 *head = -1;
1207 return -1;
1209 else if (i == *head)
1210 *head = next;
1211 cache[next].prev = prev;
1212 cache[prev].next = next;
1213 return next;
1218 Pop the head item from the list starting at *head, returning the index of the
1219 item, or -1 if the list is already empty.
1221 static inline int lla_pop_head (int *head)
1223 int i = *head;
1224 if (i != -1)
1225 lla_pop_item(head, i);
1226 return i;
1230 Insert the item at index i before the one at index p.
1232 static inline void lla_insert (int i, int p)
1234 int next = p;
1235 int prev = cache[next].prev;
1236 cache[next].prev = i;
1237 cache[prev].next = i;
1238 cache[i].next = next;
1239 cache[i].prev = prev;
1244 Insert the item at index i at the end of the list starting at *head.
1246 static inline void lla_insert_tail (int *head, int i)
1248 if (*head == -1)
1250 *head = i;
1251 cache[i].next = i;
1252 cache[i].prev = i;
1253 } else
1254 lla_insert(i, *head);
1258 Insert the item at index i before the one at index p.
1260 static inline void lla_insert_after(int i, int p)
1262 p = cache[p].next;
1263 lla_insert(i, p);
1268 Insert the item at index i before the one at index p in the list starting at
1269 *head
1271 static inline void lla_insert_before(int *head, int i, int p)
1273 lla_insert(i, p);
1274 if (*head == p)
1275 *head = i;
1280 Free the used slide at index i, and its buffer, and move it to the free
1281 slides list.
1283 static inline void free_slide(int i)
1285 if (cache[i].hid != empty_slide_hid)
1286 buflib_free(&buf_ctx, cache[i].hid);
1287 cache[i].index = -1;
1288 lla_pop_item(&cache_used, i);
1289 lla_insert_tail(&cache_free, i);
1290 if (cache_used == -1)
1292 cache_right_index = -1;
1293 cache_left_index = -1;
1294 cache_center_index = -1;
1300 Free one slide ranked above the given priority. If no such slide can be found,
1301 return false.
1303 static bool free_slide_prio(int prio)
1305 if (cache_used == -1)
1306 return false;
1307 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1308 int prio_l = cache[l].index < center_index ?
1309 center_index - cache[l].index : 0;
1310 int prio_r = cache[r].index > center_index ?
1311 cache[r].index - center_index : 0;
1312 if (prio_l > prio_r)
1314 i = l;
1315 prio_max = prio_l;
1316 } else {
1317 i = r;
1318 prio_max = prio_r;
1320 if (prio_max > prio)
1322 if (i == cache_left_index)
1323 cache_left_index = cache[i].next;
1324 if (i == cache_right_index)
1325 cache_right_index = cache[i].prev;
1326 free_slide(i);
1327 return true;
1328 } else
1329 return false;
1333 Read the pfraw image given as filename and return the hid of the buffer
1335 int read_pfraw(char* filename, int prio)
1337 struct pfraw_header bmph;
1338 int fh = rb->open(filename, O_RDONLY);
1339 if( fh < 0 )
1340 return empty_slide_hid;
1341 else
1342 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1344 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1345 bmph.width * bmph.height;
1347 int hid;
1348 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1350 if (!hid) {
1351 rb->close( fh );
1352 return 0;
1355 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1357 bm->width = bmph.width;
1358 bm->height = bmph.height;
1359 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1361 int y;
1362 for( y = 0; y < bm->height; y++ )
1364 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1365 data += bm->width;
1367 rb->close( fh );
1368 return hid;
1373 Load the surface for the given slide_index into the cache at cache_index.
1375 static inline bool load_and_prepare_surface(const int slide_index,
1376 const int cache_index,
1377 const int prio)
1379 char tmp_path_name[MAX_PATH+1];
1380 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1381 slide_index);
1383 int hid = read_pfraw(tmp_path_name, prio);
1384 if (!hid)
1385 return false;
1387 cache[cache_index].hid = hid;
1389 if ( cache_index < SLIDE_CACHE_SIZE ) {
1390 cache[cache_index].index = slide_index;
1393 return true;
1398 Load the "next" slide that we can load, freeing old slides if needed, provided
1399 that they are further from center_index than the current slide
1401 bool load_new_slide(void)
1403 int i = -1;
1404 if (cache_center_index != -1)
1406 int next, prev;
1407 if (cache[cache_center_index].index != center_index)
1409 if (cache[cache_center_index].index < center_index)
1411 cache_center_index = seek_right_while(cache_center_index,
1412 cache[next_].index <= center_index);
1413 prev = cache_center_index;
1414 next = cache[cache_center_index].next;
1416 else
1418 cache_center_index = seek_left_while(cache_center_index,
1419 cache[next_].index >= center_index);
1420 next = cache_center_index;
1421 prev = cache[cache_center_index].prev;
1423 if (cache[cache_center_index].index != center_index)
1425 if (cache_free == -1)
1426 free_slide_prio(0);
1427 i = lla_pop_head(&cache_free);
1428 if (!load_and_prepare_surface(center_index, i, 0))
1429 goto fail_and_refree;
1430 if (cache[next].index == -1)
1432 if (cache[prev].index == -1)
1433 goto insert_first_slide;
1434 else
1435 next = cache[prev].next;
1437 lla_insert(i, next);
1438 if (cache[i].index < cache[cache_used].index)
1439 cache_used = i;
1440 cache_center_index = i;
1441 cache_left_index = i;
1442 cache_right_index = i;
1443 return true;
1446 if (cache[cache_left_index].index >
1447 cache[cache_center_index].index)
1448 cache_left_index = cache_center_index;
1449 if (cache[cache_right_index].index <
1450 cache[cache_center_index].index)
1451 cache_right_index = cache_center_index;
1452 cache_left_index = seek_left_while(cache_left_index,
1453 cache[ind_].index - 1 == cache[next_].index);
1454 cache_right_index = seek_right_while(cache_right_index,
1455 cache[ind_].index - 1 == cache[next_].index);
1456 int prio_l = cache[cache_center_index].index -
1457 cache[cache_left_index].index + 1;
1458 int prio_r = cache[cache_right_index].index -
1459 cache[cache_center_index].index + 1;
1460 if ((prio_l < prio_r ||
1461 cache[cache_right_index].index >= number_of_slides) &&
1462 cache[cache_left_index].index > 0)
1464 if (cache_free == -1 && !free_slide_prio(prio_l))
1465 return false;
1466 i = lla_pop_head(&cache_free);
1467 if (load_and_prepare_surface(cache[cache_left_index].index
1468 - 1, i, prio_l))
1470 lla_insert_before(&cache_used, i, cache_left_index);
1471 cache_left_index = i;
1472 return true;
1474 } else if(cache[cache_right_index].index < number_of_slides - 1)
1476 if (cache_free == -1 && !free_slide_prio(prio_r))
1477 return false;
1478 i = lla_pop_head(&cache_free);
1479 if (load_and_prepare_surface(cache[cache_right_index].index
1480 + 1, i, prio_r))
1482 lla_insert_after(i, cache_right_index);
1483 cache_right_index = i;
1484 return true;
1487 } else {
1488 i = lla_pop_head(&cache_free);
1489 if (load_and_prepare_surface(center_index, i, 0))
1491 insert_first_slide:
1492 cache[i].next = i;
1493 cache[i].prev = i;
1494 cache_center_index = i;
1495 cache_left_index = i;
1496 cache_right_index = i;
1497 cache_used = i;
1498 return true;
1501 fail_and_refree:
1502 if (i != -1)
1504 lla_insert_tail(&cache_free, i);
1506 return false;
1511 Get a slide from the buffer
1513 static inline struct dim *get_slide(const int hid)
1515 if (!hid)
1516 return NULL;
1518 struct dim *bmp;
1520 bmp = buflib_get_data(&buf_ctx, hid);
1522 return bmp;
1527 Return the requested surface
1529 static inline struct dim *surface(const int slide_index)
1531 if (slide_index < 0)
1532 return 0;
1533 if (slide_index >= number_of_slides)
1534 return 0;
1535 int i;
1536 if ((i = cache_used ) != -1)
1538 do {
1539 if (cache[i].index == slide_index)
1540 return get_slide(cache[i].hid);
1541 i = cache[i].next;
1542 } while (i != cache_used);
1544 return get_slide(empty_slide_hid);
1548 adjust slides so that they are in "steady state" position
1550 void reset_slides(void)
1552 center_slide.angle = 0;
1553 center_slide.cx = 0;
1554 center_slide.cy = 0;
1555 center_slide.distance = 0;
1556 center_slide.slide_index = center_index;
1558 int i;
1559 for (i = 0; i < num_slides; i++) {
1560 struct slide_data *si = &left_slides[i];
1561 si->angle = itilt;
1562 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1563 si->cy = offsetY;
1564 si->slide_index = center_index - 1 - i;
1565 si->distance = 0;
1568 for (i = 0; i < num_slides; i++) {
1569 struct slide_data *si = &right_slides[i];
1570 si->angle = -itilt;
1571 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1572 si->cy = offsetY;
1573 si->slide_index = center_index + 1 + i;
1574 si->distance = 0;
1580 Updates look-up table and other stuff necessary for the rendering.
1581 Call this when the viewport size or slide dimension is changed.
1583 * To calculate the offset that will provide the proper margin, we use the same
1584 * projection used to render the slides. The solution for xc, the slide center,
1585 * is:
1586 * xp * (zo + xs * sin(r))
1587 * xc = xp - xs * cos(r) + ───────────────────────
1589 * TODO: support moving the side slides toward or away from the camera
1591 void recalc_offsets(void)
1593 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1594 PFreal zo;
1595 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1596 PFREAL_ONE) * zoom / 100;
1597 PFreal cosr, sinr;
1599 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1600 cosr = fcos(-itilt);
1601 sinr = fsin(-itilt);
1602 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1603 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1604 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1605 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1606 / CAM_DIST;
1607 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1612 Fade the given color by spreading the fb_data (ushort)
1613 to an uint, multiply and compress the result back to a ushort.
1615 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1616 static inline unsigned fade_color(pix_t c, unsigned a)
1618 unsigned int result;
1619 c = swap16(c);
1620 a = (a + 2) & 0x1fc;
1621 result = ((c & 0xf81f) * a) & 0xf81f00;
1622 result |= ((c & 0x7e0) * a) & 0x7e000;
1623 result >>= 8;
1624 return swap16(result);
1626 #elif LCD_PIXELFORMAT == RGB565
1627 static inline unsigned fade_color(pix_t c, unsigned a)
1629 unsigned int result;
1630 a = (a + 2) & 0x1fc;
1631 result = ((c & 0xf81f) * a) & 0xf81f00;
1632 result |= ((c & 0x7e0) * a) & 0x7e000;
1633 result >>= 8;
1634 return result;
1636 #else
1637 static inline unsigned fade_color(pix_t c, unsigned a)
1639 unsigned val = c;
1640 return MULUQ(val, a) >> 8;
1642 #endif
1645 * Render a single slide
1646 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1647 * on the slide from its center, zo is the slide's depth offset from the plane
1648 * of the display, r is the angle at which the slide is tilted, and xp is the
1649 * point on the display corresponding to xs on the slide, the projection
1650 * formulas are:
1652 * z * (xc + xs * cos(r))
1653 * xp = ──────────────────────
1654 * z + zo + xs * sin(r)
1656 * z * (xc - xp) - xp * zo
1657 * xs = ────────────────────────
1658 * xp * sin(r) - z * cos(r)
1660 * We use the xp projection once, to find the left edge of the slide on the
1661 * display. From there, we use the xs reverse projection to find the horizontal
1662 * offset from the slide center of each column on the screen, until we reach
1663 * the right edge of the slide, or the screen. The reverse projection can be
1664 * optimized by saving the numerator and denominator of the fraction, which can
1665 * then be incremented by (z + zo) and sin(r) respectively.
1667 void render_slide(struct slide_data *slide, const int alpha)
1669 struct dim *bmp = surface(slide->slide_index);
1670 if (!bmp) {
1671 return;
1673 if (slide->angle > 255 || slide->angle < -255)
1674 return;
1675 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1677 const int sw = bmp->width;
1678 const int sh = bmp->height;
1679 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1680 const int w = LCD_WIDTH;
1682 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1684 if (alpha == 256) { /* opaque -> copy table */
1685 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1686 } else { /* precalculate faded table */
1687 int i, lalpha;
1688 for (i = 0; i < REFLECT_HEIGHT; i++) {
1689 lalpha = reflect_table[i];
1690 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1694 PFreal cosr = fcos(slide->angle);
1695 PFreal sinr = fsin(slide->angle);
1696 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1697 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1698 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1699 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1700 (CAM_DIST_R + zo + fmul(xs,sinr)));
1702 /* Since we're finding the screen position of the left edge of the slide,
1703 * we round up.
1705 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1706 >> PFREAL_SHIFT;
1707 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1708 if (xi >= w) {
1709 return;
1711 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1712 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1713 xs = fdiv(xsnum, xsden);
1715 xsnumi = -CAM_DIST_R - zo;
1716 xsdeni = sinr;
1717 int x;
1718 int dy = PFREAL_ONE;
1719 for (x = xi; x < w; x++) {
1720 int column = (xs - slide_left) / PFREAL_ONE;
1721 if (column >= sw)
1722 break;
1723 if (zo || slide->angle)
1724 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1726 const pix_t *ptr = &src[column * bmp->height];
1727 const int pixelstep = BUFFER_WIDTH;
1729 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1730 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1731 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1733 if (alpha == 256) {
1734 while (p >= plim) {
1735 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1736 p -= dy;
1737 pixel -= pixelstep;
1739 } else {
1740 while (p >= plim) {
1741 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1742 p -= dy;
1743 pixel -= pixelstep;
1746 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1747 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1748 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1749 p + (LCD_HEIGHT/2) * dy);
1750 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1752 if (alpha == 256) {
1753 while (p < plim) {
1754 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1755 p += dy;
1756 pixel += pixelstep;
1758 } else {
1759 while (p < plim) {
1760 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1761 p += dy;
1762 pixel += pixelstep;
1765 while (p < plim2) {
1766 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1767 int lalpha = reftab[ty];
1768 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1769 p += dy;
1770 pixel += pixelstep;
1773 if (zo || slide->angle)
1775 xsnum += xsnumi;
1776 xsden += xsdeni;
1777 xs = fdiv(xsnum, xsden);
1778 } else
1779 xs += PFREAL_ONE;
1782 /* let the music play... */
1783 rb->yield();
1784 return;
1789 Jump the the given slide_index
1791 static inline void set_current_slide(const int slide_index)
1793 int old_center_index = center_index;
1794 step = 0;
1795 center_index = fbound(slide_index, 0, number_of_slides - 1);
1796 if (old_center_index != center_index)
1797 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1798 target = center_index;
1799 slide_frame = slide_index << 16;
1800 reset_slides();
1804 Start the animation for changing slides
1806 void start_animation(void)
1808 step = (target < center_slide.slide_index) ? -1 : 1;
1809 pf_state = pf_scrolling;
1813 Go to the previous slide
1815 void show_previous_slide(void)
1817 if (step == 0) {
1818 if (center_index > 0) {
1819 target = center_index - 1;
1820 start_animation();
1822 } else if ( step > 0 ) {
1823 target = center_index;
1824 start_animation();
1825 } else {
1826 target = fmax(0, center_index - 2);
1832 Go to the next slide
1834 void show_next_slide(void)
1836 if (step == 0) {
1837 if (center_index < number_of_slides - 1) {
1838 target = center_index + 1;
1839 start_animation();
1841 } else if ( step < 0 ) {
1842 target = center_index;
1843 start_animation();
1844 } else {
1845 target = fmin(center_index + 2, number_of_slides - 1);
1851 Render the slides. Updates only the offscreen buffer.
1853 void render_all_slides(void)
1855 MYLCD(set_background)(G_BRIGHT(0));
1856 /* TODO: Optimizes this by e.g. invalidating rects */
1857 MYLCD(clear_display)();
1859 int nleft = num_slides;
1860 int nright = num_slides;
1862 int index;
1863 if (step == 0) {
1864 /* no animation, boring plain rendering */
1865 for (index = nleft - 2; index >= 0; index--) {
1866 int alpha = (index < nleft - 2) ? 256 : 128;
1867 alpha -= extra_fade;
1868 if (alpha > 0 )
1869 render_slide(&left_slides[index], alpha);
1871 for (index = nright - 2; index >= 0; index--) {
1872 int alpha = (index < nright - 2) ? 256 : 128;
1873 alpha -= extra_fade;
1874 if (alpha > 0 )
1875 render_slide(&right_slides[index], alpha);
1877 } else {
1878 /* the first and last slide must fade in/fade out */
1879 for (index = nleft - 1; index >= 0; index--) {
1880 int alpha = 256;
1881 if (index == nleft - 1)
1882 alpha = (step > 0) ? 0 : 128 - fade / 2;
1883 if (index == nleft - 2)
1884 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1885 if (index == nleft - 3)
1886 alpha = (step > 0) ? 256 - fade / 2 : 256;
1887 render_slide(&left_slides[index], alpha);
1889 for (index = nright - 1; index >= 0; index--) {
1890 int alpha = (index < nright - 2) ? 256 : 128;
1891 if (index == nright - 1)
1892 alpha = (step > 0) ? fade / 2 : 0;
1893 if (index == nright - 2)
1894 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1895 if (index == nright - 3)
1896 alpha = (step > 0) ? 256 : 128 + fade / 2;
1897 render_slide(&right_slides[index], alpha);
1900 render_slide(&center_slide, 256);
1905 Updates the animation effect. Call this periodically from a timer.
1907 void update_scroll_animation(void)
1909 if (step == 0)
1910 return;
1912 int speed = 16384;
1913 int i;
1915 /* deaccelerate when approaching the target */
1916 if (true) {
1917 const int max = 2 * 65536;
1919 int fi = slide_frame;
1920 fi -= (target << 16);
1921 if (fi < 0)
1922 fi = -fi;
1923 fi = fmin(fi, max);
1925 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1926 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1929 slide_frame += speed * step;
1931 int index = slide_frame >> 16;
1932 int pos = slide_frame & 0xffff;
1933 int neg = 65536 - pos;
1934 int tick = (step < 0) ? neg : pos;
1935 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1937 /* the leftmost and rightmost slide must fade away */
1938 fade = pos / 256;
1940 if (step < 0)
1941 index++;
1942 if (center_index != index) {
1943 center_index = index;
1944 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1945 slide_frame = index << 16;
1946 center_slide.slide_index = center_index;
1947 for (i = 0; i < num_slides; i++)
1948 left_slides[i].slide_index = center_index - 1 - i;
1949 for (i = 0; i < num_slides; i++)
1950 right_slides[i].slide_index = center_index + 1 + i;
1953 center_slide.angle = (step * tick * itilt) >> 16;
1954 center_slide.cx = -step * fmul(offsetX, ftick);
1955 center_slide.cy = fmul(offsetY, ftick);
1957 if (center_index == target) {
1958 reset_slides();
1959 pf_state = pf_idle;
1960 step = 0;
1961 fade = 256;
1962 return;
1965 for (i = 0; i < num_slides; i++) {
1966 struct slide_data *si = &left_slides[i];
1967 si->angle = itilt;
1968 si->cx =
1969 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1970 * slide_spacing * ftick);
1971 si->cy = offsetY;
1974 for (i = 0; i < num_slides; i++) {
1975 struct slide_data *si = &right_slides[i];
1976 si->angle = -itilt;
1977 si->cx =
1978 offsetX + slide_spacing * i * PFREAL_ONE - step
1979 * slide_spacing * ftick;
1980 si->cy = offsetY;
1983 if (step > 0) {
1984 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1985 right_slides[0].angle = -(neg * itilt) >> 16;
1986 right_slides[0].cx = fmul(offsetX, ftick);
1987 right_slides[0].cy = fmul(offsetY, ftick);
1988 } else {
1989 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1990 left_slides[0].angle = (pos * itilt) >> 16;
1991 left_slides[0].cx = -fmul(offsetX, ftick);
1992 left_slides[0].cy = fmul(offsetY, ftick);
1995 /* must change direction ? */
1996 if (target < index)
1997 if (step > 0)
1998 step = -1;
1999 if (target > index)
2000 if (step < 0)
2001 step = 1;
2006 Cleanup the plugin
2008 void cleanup(void *parameter)
2010 (void) parameter;
2011 /* Turn on backlight timeout (revert to settings) */
2012 backlight_use_settings(); /* backlight control in lib/helper.c */
2014 #ifdef USEGSLIB
2015 grey_release();
2016 #endif
2020 Create the "?" slide, that is shown while loading
2021 or when no cover was found.
2023 int create_empty_slide(bool force)
2025 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2026 struct bitmap input_bmp;
2027 int ret;
2028 input_bmp.width = DISPLAY_WIDTH;
2029 input_bmp.height = DISPLAY_HEIGHT;
2030 #if LCD_DEPTH > 1
2031 input_bmp.format = FORMAT_NATIVE;
2032 #endif
2033 input_bmp.data = (char*)buf;
2034 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2035 buf_size,
2036 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2037 &format_transposed);
2038 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2039 return false;
2042 return true;
2046 Shows the album name setting menu
2048 int album_name_menu(void)
2050 int selection = show_album_name;
2052 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2053 "Hide album title", "Show at the bottom", "Show at the top");
2054 rb->do_menu(&album_name_menu, &selection, NULL, false);
2056 show_album_name = selection;
2057 return GO_TO_PREVIOUS;
2061 Shows the settings menu
2063 int settings_menu(void)
2065 int selection = 0;
2066 bool old_val;
2068 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2069 "Spacing", "Centre margin", "Number of slides", "Zoom",
2070 "Show album title", "Resize Covers", "Rebuild cache");
2072 do {
2073 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2074 switch(selection) {
2075 case 0:
2076 rb->set_bool("Show FPS", &show_fps);
2077 reset_track_list();
2078 break;
2080 case 1:
2081 rb->set_int("Spacing between slides", "", 1,
2082 &slide_spacing,
2083 NULL, 1, 0, 100, NULL );
2084 recalc_offsets();
2085 reset_slides();
2086 break;
2088 case 2:
2089 rb->set_int("Centre margin", "", 1,
2090 &center_margin,
2091 NULL, 1, 0, 80, NULL );
2092 recalc_offsets();
2093 reset_slides();
2094 break;
2096 case 3:
2097 rb->set_int("Number of slides", "", 1, &num_slides,
2098 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2099 recalc_offsets();
2100 reset_slides();
2101 break;
2103 case 4:
2104 rb->set_int("Zoom", "", 1, &zoom,
2105 NULL, 1, 10, 300, NULL );
2106 recalc_offsets();
2107 reset_slides();
2108 break;
2109 case 5:
2110 album_name_menu();
2111 reset_track_list();
2112 recalc_offsets();
2113 reset_slides();
2114 break;
2115 case 6:
2116 old_val = resize;
2117 rb->set_bool("Resize Covers", &resize);
2118 if (old_val == resize) /* changed? */
2119 break;
2120 /* fallthrough if changed, since cache needs to be rebuilt */
2121 case 7:
2122 cache_version = 0;
2123 rb->remove(EMPTY_SLIDE);
2124 rb->splash(HZ, "Cache will be rebuilt on next restart");
2125 break;
2127 case MENU_ATTACHED_USB:
2128 return PLUGIN_USB_CONNECTED;
2130 } while ( selection >= 0 );
2131 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2132 return 0;
2136 Show the main menu
2138 enum {
2139 #if PF_PLAYBACK_CAPABLE
2140 PF_MENU_PLAYBACK_CONTROl,
2141 #endif
2142 PF_MENU_SETTINGS,
2143 PF_MENU_RETURN,
2144 PF_MENU_QUIT,
2147 int main_menu(void)
2149 int selection = 0;
2150 int result;
2152 #if LCD_DEPTH > 1
2153 rb->lcd_set_foreground(N_BRIGHT(255));
2154 #endif
2156 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2157 #if PF_PLAYBACK_CAPABLE
2158 "Playback Control",
2159 #endif
2160 "Settings", "Return", "Quit");
2161 while (1) {
2162 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2163 #if PF_PLAYBACK_CAPABLE
2164 case PF_MENU_PLAYBACK_CONTROl: /* Playback Control */
2165 playback_control(NULL);
2166 break;
2167 #endif
2168 case PF_MENU_SETTINGS:
2169 result = settings_menu();
2170 if ( result != 0 ) return result;
2171 break;
2173 case PF_MENU_RETURN:
2174 return 0;
2176 case PF_MENU_QUIT:
2177 return -1;
2179 case MENU_ATTACHED_USB:
2180 return PLUGIN_USB_CONNECTED;
2182 default:
2183 return 0;
2189 Animation step for zooming into the current cover
2191 void update_cover_in_animation(void)
2193 cover_animation_keyframe++;
2194 if( cover_animation_keyframe < 20 ) {
2195 center_slide.distance-=5;
2196 center_slide.angle+=1;
2197 extra_fade += 13;
2199 else if( cover_animation_keyframe < 35 ) {
2200 center_slide.angle+=16;
2202 else {
2203 cover_animation_keyframe = 0;
2204 pf_state = pf_show_tracks;
2209 Animation step for zooming out the current cover
2211 void update_cover_out_animation(void)
2213 cover_animation_keyframe++;
2214 if( cover_animation_keyframe <= 15 ) {
2215 center_slide.angle-=16;
2217 else if( cover_animation_keyframe < 35 ) {
2218 center_slide.distance+=5;
2219 center_slide.angle-=1;
2220 extra_fade -= 13;
2222 else {
2223 cover_animation_keyframe = 0;
2224 pf_state = pf_idle;
2229 Draw a blue gradient at y with height h
2231 static inline void draw_gradient(int y, int h)
2233 static int r, inc, c;
2234 inc = (100 << 8) / h;
2235 c = 0;
2236 selected_track_pulse = (selected_track_pulse+1) % 10;
2237 int c2 = selected_track_pulse - 5;
2238 for (r=0; r<h; r++) {
2239 #ifdef HAVE_LCD_COLOR
2240 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2241 c2+250-(c >> 8)));
2242 #else
2243 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2244 #endif
2245 MYLCD(hline)(0, LCD_WIDTH, r+y);
2246 if ( r > h/2 )
2247 c-=inc;
2248 else
2249 c+=inc;
2254 static void track_list_yh(int char_height)
2256 switch (show_album_name)
2258 case album_name_hide:
2259 track_list_y = (show_fps ? char_height : 0);
2260 track_list_h = LCD_HEIGHT - track_list_y;
2261 break;
2262 case album_name_bottom:
2263 track_list_y = (show_fps ? char_height : 0);
2264 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2265 break;
2266 default: /* case album_name_top */
2267 track_list_y = char_height * 2;
2268 track_list_h = LCD_HEIGHT - track_list_y -
2269 (show_fps ? char_height : 0);
2270 break;
2275 Reset the track list after a album change
2277 void reset_track_list(void)
2279 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2280 track_list_yh(albumtxt_h);
2281 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2282 start_index_track_list = 0;
2283 track_scroll_index = 0;
2284 track_scroll_dir = 1;
2285 selected_track = 0;
2287 /* let the tracklist start more centered
2288 * if the screen isn't filled with tracks */
2289 if (track_count*albumtxt_h < track_list_h)
2291 track_list_h = track_count * albumtxt_h;
2292 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2297 Display the list of tracks
2299 void show_track_list(void)
2301 MYLCD(clear_display)();
2302 if ( center_slide.slide_index != track_index ) {
2303 create_track_index(center_slide.slide_index);
2304 reset_track_list();
2306 static int titletxt_w, titletxt_x, color, titletxt_h;
2307 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2309 int titletxt_y = track_list_y;
2310 int track_i;
2311 track_i = start_index_track_list;
2312 for (;track_i < track_list_visible_entries+start_index_track_list;
2313 track_i++)
2315 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2316 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2317 if ( track_i == selected_track ) {
2318 draw_gradient(titletxt_y, titletxt_h);
2319 MYLCD(set_foreground)(G_BRIGHT(255));
2320 if (titletxt_w > LCD_WIDTH ) {
2321 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2322 track_scroll_dir = 1;
2323 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2324 track_scroll_index += track_scroll_dir*2;
2325 titletxt_x = track_scroll_index;
2327 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2329 else {
2330 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2331 MYLCD(set_foreground)(G_BRIGHT(color));
2332 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2334 titletxt_y += titletxt_h;
2338 void select_next_track(void)
2340 if ( selected_track < track_count - 1 ) {
2341 selected_track++;
2342 track_scroll_index = 0;
2343 track_scroll_dir = 1;
2344 if (selected_track==(track_list_visible_entries+start_index_track_list))
2345 start_index_track_list++;
2349 void select_prev_track(void)
2351 if (selected_track > 0 ) {
2352 if (selected_track==start_index_track_list) start_index_track_list--;
2353 track_scroll_index = 0;
2354 track_scroll_dir = 1;
2355 selected_track--;
2359 #if PF_PLAYBACK_CAPABLE
2361 * Puts the current tracklist into a newly created playlist and starts playling
2363 void start_playback(void)
2365 static int old_playlist = -1, old_shuffle = 0;
2366 int count = 0;
2367 int position = selected_track;
2368 int shuffle = rb->global_settings->playlist_shuffle;
2369 /* reuse existing playlist if possible
2370 * regenerate if shuffle is on or changed, since playlist index and
2371 * selected track are "out of sync" */
2372 if (!shuffle && center_slide.slide_index == old_playlist
2373 && (old_shuffle == shuffle))
2375 goto play;
2377 /* First, replace the current playlist with a new one */
2378 else if (rb->playlist_remove_all_tracks(NULL) == 0
2379 && rb->playlist_create(NULL, NULL) == 0)
2381 do {
2382 rb->yield();
2383 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2384 PLAYLIST_INSERT_LAST, false, true) < 0)
2385 break;
2386 } while(++count < track_count);
2387 rb->playlist_sync(NULL);
2389 else
2390 return;
2392 if (rb->global_settings->playlist_shuffle)
2393 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2394 play:
2395 /* TODO: can we adjust selected_track if !play_selected ?
2396 * if shuffle, we can't predict the playing track easily, and for either
2397 * case the track list doesn't get auto scrolled*/
2398 rb->playlist_start(position, 0);
2399 old_playlist = center_slide.slide_index;
2400 old_shuffle = shuffle;
2402 #endif
2404 Draw the current album name
2406 void draw_album_text(void)
2408 if (0 == show_album_name)
2409 return;
2411 int albumtxt_w, albumtxt_h;
2412 int albumtxt_y = 0;
2414 char *albumtxt;
2415 int c;
2416 /* Draw album text */
2417 if ( pf_state == pf_scrolling ) {
2418 c = ((slide_frame & 0xffff )/ 255);
2419 if (step < 0) c = 255-c;
2420 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2421 albumtxt = get_album_name(center_index+step);
2422 c = (c-128)*2;
2424 else {
2425 albumtxt = get_album_name(center_index);
2426 c = (128-c)*2;
2429 else {
2430 c= 255;
2431 albumtxt = get_album_name(center_index);
2434 MYLCD(set_foreground)(G_BRIGHT(c));
2435 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2436 if (center_index != prev_center_index) {
2437 albumtxt_x = 0;
2438 albumtxt_dir = -1;
2439 prev_center_index = center_index;
2442 if (show_album_name == album_name_top)
2443 albumtxt_y = albumtxt_h / 2;
2444 else
2445 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2447 if (albumtxt_w > LCD_WIDTH ) {
2448 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2449 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2450 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2451 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2452 albumtxt_x += albumtxt_dir;
2455 else {
2456 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2463 Display an error message and wait for input.
2465 void error_wait(const char *message)
2467 rb->splashf(0, "%s. Press any button to continue.", message);
2468 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2469 rb->yield();
2470 rb->sleep(2 * HZ);
2474 Main function that also contain the main plasma
2475 algorithm.
2477 int main(void)
2479 int ret;
2481 rb->lcd_setfont(FONT_UI);
2482 draw_splashscreen();
2484 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2485 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2486 error_wait("Could not create directory " CACHE_PREFIX);
2487 return PLUGIN_ERROR;
2491 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2493 init_reflect_table();
2495 ALIGN_BUFFER(buf, buf_size, 4);
2496 ret = create_album_index();
2497 if (ret == ERROR_BUFFER_FULL) {
2498 error_wait("Not enough memory for album names");
2499 return PLUGIN_ERROR;
2500 } else if (ret == ERROR_NO_ALBUMS) {
2501 error_wait("No albums found. Please enable database");
2502 return PLUGIN_ERROR;
2505 ALIGN_BUFFER(buf, buf_size, 4);
2506 number_of_slides = album_count;
2507 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2508 error_wait("Could not create album art cache");
2509 return PLUGIN_ERROR;
2512 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2513 error_wait("Could not load the empty slide");
2514 return PLUGIN_ERROR;
2516 cache_version = CACHE_VERSION;
2517 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2520 #ifdef USEGSLIB
2521 long grey_buf_used;
2522 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2523 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2525 error_wait("Greylib init failed!");
2526 return PLUGIN_ERROR;
2528 grey_setfont(FONT_UI);
2529 buf_size -= grey_buf_used;
2530 buf = (void*)(grey_buf_used + (char*)buf);
2531 #endif
2532 buflib_init(&buf_ctx, (void *)buf, buf_size);
2534 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2536 error_wait("Unable to load empty slide image");
2537 return PLUGIN_ERROR;
2540 if (!create_pf_thread()) {
2541 error_wait("Cannot create thread!");
2542 return PLUGIN_ERROR;
2545 int i;
2547 /* initialize */
2548 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2549 cache[i].hid = 0;
2550 cache[i].index = 0;
2551 cache[i].next = i + 1;
2552 cache[i].prev = i - 1;
2554 cache[0].prev = i - 1;
2555 cache[i - 1].next = 0;
2556 cache_free = 0;
2557 buffer = LCD_BUF;
2559 pf_state = pf_idle;
2561 track_index = -1;
2562 extra_fade = 0;
2563 slide_frame = 0;
2564 step = 0;
2565 target = 0;
2566 fade = 256;
2568 recalc_offsets();
2569 reset_slides();
2571 char fpstxt[10];
2572 int button;
2574 int frames = 0;
2575 long last_update = *rb->current_tick;
2576 long current_update;
2577 long update_interval = 100;
2578 int fps = 0;
2579 int fpstxt_y;
2581 bool instant_update;
2582 #ifdef USEGSLIB
2583 grey_show(true);
2584 grey_set_drawmode(DRMODE_FG);
2585 #endif
2586 rb->lcd_set_drawmode(DRMODE_FG);
2587 while (true) {
2588 current_update = *rb->current_tick;
2589 frames++;
2591 /* Initial rendering */
2592 instant_update = false;
2594 /* Handle states */
2595 switch ( pf_state ) {
2596 case pf_scrolling:
2597 update_scroll_animation();
2598 render_all_slides();
2599 instant_update = true;
2600 break;
2601 case pf_cover_in:
2602 update_cover_in_animation();
2603 render_all_slides();
2604 instant_update = true;
2605 break;
2606 case pf_cover_out:
2607 update_cover_out_animation();
2608 render_all_slides();
2609 instant_update = true;
2610 break;
2611 case pf_show_tracks:
2612 show_track_list();
2613 break;
2614 case pf_idle:
2615 render_all_slides();
2616 break;
2619 /* Calculate FPS */
2620 if (current_update - last_update > update_interval) {
2621 fps = frames * HZ / (current_update - last_update);
2622 last_update = current_update;
2623 frames = 0;
2625 /* Draw FPS */
2626 if (show_fps)
2628 #ifdef USEGSLIB
2629 MYLCD(set_foreground)(G_BRIGHT(255));
2630 #else
2631 MYLCD(set_foreground)(G_PIX(255,0,0));
2632 #endif
2633 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2634 if (show_album_name == album_name_top)
2635 fpstxt_y = LCD_HEIGHT -
2636 rb->screens[SCREEN_MAIN]->getcharheight();
2637 else
2638 fpstxt_y = 0;
2639 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2641 draw_album_text();
2644 /* Copy offscreen buffer to LCD and give time to other threads */
2645 MYLCD(update)();
2646 rb->yield();
2648 /*/ Handle buttons */
2649 button = rb->get_custom_action(CONTEXT_PLUGIN
2650 #if !defined(HAVE_SCROLLWHEEL)
2651 |(pf_state == pf_show_tracks ? 1 : 0)
2652 #endif
2653 ,instant_update ? 0 : HZ/16,
2654 get_context_map);
2656 switch (button) {
2657 case PF_QUIT:
2658 return PLUGIN_OK;
2660 case PF_BACK:
2661 if ( pf_state == pf_show_tracks )
2663 buflib_buffer_in(&buf_ctx, borrowed);
2664 borrowed = 0;
2665 track_index = -1;
2666 pf_state = pf_cover_out;
2668 if (pf_state == pf_idle || pf_state == pf_scrolling)
2669 return PLUGIN_OK;
2670 break;
2672 case PF_MENU:
2673 #ifdef USEGSLIB
2674 grey_show(false);
2675 #endif
2676 ret = main_menu();
2677 if ( ret == -1 ) return PLUGIN_OK;
2678 if ( ret != 0 ) return ret;
2679 #ifdef USEGSLIB
2680 grey_show(true);
2681 #endif
2682 MYLCD(set_drawmode)(DRMODE_FG);
2683 break;
2685 case PF_NEXT:
2686 case PF_NEXT_REPEAT:
2687 if ( pf_state == pf_show_tracks )
2688 select_next_track();
2689 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2690 show_next_slide();
2691 break;
2693 case PF_PREV:
2694 case PF_PREV_REPEAT:
2695 if ( pf_state == pf_show_tracks )
2696 select_prev_track();
2697 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2698 show_previous_slide();
2699 break;
2701 case PF_SELECT:
2702 if ( pf_state == pf_idle ) {
2703 pf_state = pf_cover_in;
2705 else if ( pf_state == pf_show_tracks ) {
2706 #if PF_PLAYBACK_CAPABLE
2707 start_playback();
2708 #endif
2710 break;
2712 default:
2713 if (rb->default_event_handler_ex(button, cleanup, NULL)
2714 == SYS_USB_CONNECTED)
2715 return PLUGIN_USB_CONNECTED;
2716 break;
2723 /*************************** Plugin entry point ****************************/
2725 enum plugin_status plugin_start(const void *parameter)
2727 int ret;
2728 (void) parameter;
2729 #if LCD_DEPTH > 1
2730 rb->lcd_set_backdrop(NULL);
2731 #endif
2732 /* Turn off backlight timeout */
2733 backlight_force_on(); /* backlight control in lib/helper.c */
2734 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2735 rb->cpu_boost(true);
2736 #endif
2737 #if PF_PLAYBACK_CAPABLE
2738 buf = rb->plugin_get_buffer(&buf_size);
2739 #else
2740 buf = rb->plugin_get_audio_buffer(&buf_size);
2741 #ifndef SIMULATOR
2742 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2744 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2745 buf_size = MIN(buf_size, tmp_size);
2747 #endif
2748 #endif
2749 ret = main();
2750 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2751 rb->cpu_boost(false);
2752 #endif
2753 if ( ret == PLUGIN_OK ) {
2754 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2755 CONFIG_VERSION))
2757 rb->splash(HZ, "Error writing config.");
2758 ret = PLUGIN_ERROR;
2762 end_pf_thread();
2763 cleanup(NULL);
2764 return ret;