Renamed button description layer to 'Text'.
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob546afad2176756f66c936071b99c9d8cfe204130
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/mylcd.h"
34 #include "lib/feature_wrappers.h"
35 #include "lib/buflib.h"
37 PLUGIN_HEADER
39 /******************************* Globals ***********************************/
42 * Targets which use plugin_get_audio_buffer() can't have playback from
43 * within pictureflow itself, as the whole core audio buffer is occupied */
44 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
46 #if PF_PLAYBACK_CAPABLE
47 #include "lib/playback_control.h"
48 #endif
50 #define PF_PREV ACTION_STD_PREV
51 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
52 #define PF_NEXT ACTION_STD_NEXT
53 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
54 #define PF_SELECT ACTION_STD_OK
55 #define PF_CONTEXT ACTION_STD_CONTEXT
56 #define PF_BACK ACTION_STD_CANCEL
57 #define PF_MENU ACTION_STD_MENU
58 #define PF_WPS ACTION_TREE_WPS
60 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
61 #define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2)
63 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
64 CONFIG_KEYPAD == SAMSUNG_YH_PAD
65 #define USE_CORE_PREVNEXT
66 #endif
68 #ifndef USE_CORE_PREVNEXT
69 /* scrollwheel targets use the wheel, just as they do in lists,
70 * so there's no need for a special context,
71 * others use left/right here too (as oppsed to up/down in lists) */
72 const struct button_mapping pf_context_album_scroll[] =
74 #ifdef HAVE_TOUCHSCREEN
75 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
76 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
77 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
78 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
79 #endif
80 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
81 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
82 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
83 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
84 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
85 #else
86 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
87 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
88 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
89 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
90 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
91 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
92 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
93 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
94 #endif
95 #if CONFIG_KEYPAD == ONDIO_PAD
96 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
97 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
98 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
99 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
100 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
101 #endif
102 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
104 #endif /* !USE_CORE_PREVNEXT */
106 const struct button_mapping pf_context_buttons[] =
108 #ifdef HAVE_TOUCHSCREEN
109 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
110 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
111 #endif
112 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
113 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
114 #elif CONFIG_KEYPAD == SANSA_C100_PAD
115 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
116 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
117 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
118 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
119 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
120 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
121 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
122 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
123 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
124 {PF_TRACKLIST, BUTTON_RIGHT, BUTTON_NONE},
125 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
127 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
128 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
129 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
130 #if CONFIG_KEYPAD == COWON_D2_PAD
131 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
132 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
133 #endif
134 #elif CONFIG_KEYPAD == SANSA_E200_PAD
135 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
136 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
137 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
138 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
139 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
140 || (CONFIG_KEYPAD == IPOD_4G_PAD)
141 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
142 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
143 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
144 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
145 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
146 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
147 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
148 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
149 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
150 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
151 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
152 {PF_QUIT, BUTTON_REC, BUTTON_NONE},
153 #endif
154 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
155 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
156 #else
157 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
158 #endif
160 const struct button_mapping *pf_contexts[] =
162 #ifndef USE_CORE_PREVNEXT
163 pf_context_album_scroll,
164 #endif
165 pf_context_buttons
168 #if LCD_DEPTH < 8
169 #if LCD_DEPTH > 1
170 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
171 #else /* LCD_DEPTH <= 1 */
172 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
173 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
174 #define PICTUREFLOW_DRMODE DRMODE_SOLID
175 #else
176 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
177 #endif
178 #endif /* LCD_DEPTH <= 1 */
179 #define USEGSLIB
180 GREY_INFO_STRUCT
181 #define LCD_BUF _grey_info.buffer
182 #define G_PIX(r,g,b) \
183 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
184 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
185 #define G_BRIGHT(y) (y)
186 #define BUFFER_WIDTH _grey_info.width
187 #define BUFFER_HEIGHT _grey_info.height
188 typedef unsigned char pix_t;
189 #else /* LCD_DEPTH >= 8 */
190 #define LCD_BUF rb->lcd_framebuffer
191 #define G_PIX LCD_RGBPACK
192 #define N_PIX LCD_RGBPACK
193 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
194 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
195 #define BUFFER_WIDTH LCD_WIDTH
196 #define BUFFER_HEIGHT LCD_HEIGHT
197 typedef fb_data pix_t;
198 #endif /* LCD_DEPTH >= 8 */
200 /* for fixed-point arithmetic, we need minimum 32-bit long
201 long long (64-bit) might be useful for multiplication and division */
202 #define PFreal long
203 #define PFREAL_SHIFT 10
204 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
205 #define PFREAL_ONE (1 << PFREAL_SHIFT)
206 #define PFREAL_HALF (PFREAL_ONE >> 1)
209 #define IANGLE_MAX 1024
210 #define IANGLE_MASK 1023
212 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
213 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
214 #define DISPLAY_HEIGHT REFLECT_TOP
215 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
216 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
217 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
218 (REFLECT_HEIGHT * 5))
219 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
220 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
221 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
222 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
223 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
225 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
227 #define MAX_SLIDES_COUNT 10
229 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
230 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
232 #define EV_EXIT 9999
233 #define EV_WAKEUP 1337
235 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
236 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
237 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
239 /* Error return values */
240 #define ERROR_NO_ALBUMS -1
241 #define ERROR_BUFFER_FULL -2
243 /* current version for cover cache */
244 #define CACHE_VERSION 3
245 #define CONFIG_VERSION 1
246 #define CONFIG_FILE "pictureflow.cfg"
248 /** structs we use */
250 struct slide_data {
251 int slide_index;
252 int angle;
253 PFreal cx;
254 PFreal cy;
255 PFreal distance;
258 struct slide_cache {
259 int index; /* index of the cached slide */
260 int hid; /* handle ID of the cached slide */
261 short next; /* "next" slide, with LRU last */
262 short prev; /* "previous" slide */
265 struct album_data {
266 int name_idx;
267 long seek;
270 struct track_data {
271 uint32_t sort;
272 int name_idx; /* offset to the track name */
273 long seek;
274 #if PF_PLAYBACK_CAPABLE
275 /* offset to the filename in the string, needed for playlist generation */
276 int filename_idx;
277 #endif
280 struct rect {
281 int left;
282 int right;
283 int top;
284 int bottom;
287 struct load_slide_event_data {
288 int slide_index;
289 int cache_index;
293 struct pfraw_header {
294 int32_t width; /* bmap width in pixels */
295 int32_t height; /* bmap height in pixels */
298 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
299 album_name_top };
300 static char* show_album_name_conf[] =
302 "hide",
303 "bottom",
304 "top"
307 #define MAX_SPACING 40
308 #define MAX_MARGIN 80
310 /* config values and their defaults */
311 static int slide_spacing = DISPLAY_WIDTH / 4;
312 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
313 static int num_slides = 4;
314 static int zoom = 100;
315 static bool show_fps = false;
316 static int auto_wps = 0;
317 static int last_album = 0;
318 static int backlight_mode = 0;
319 static bool resize = true;
320 static int cache_version = 0;
321 static int show_album_name = (LCD_HEIGHT > 100)
322 ? album_name_top : album_name_bottom;
324 static struct configdata config[] =
326 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
327 NULL },
328 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
329 NULL },
330 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
331 NULL },
332 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
333 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
334 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
335 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
336 { TYPE_ENUM, 0, 3, { .int_p = &show_album_name }, "show album name",
337 show_album_name_conf },
338 { TYPE_INT, 0, 2, { .int_p = &auto_wps }, "auto wps", NULL },
339 { TYPE_INT, 0, 999999, { .int_p = &last_album }, "last album", NULL },
340 { TYPE_INT, 0, 1, { .int_p = &backlight_mode }, "backlight", NULL }
343 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
345 /** below we allocate the memory we want to use **/
347 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
348 static uint8_t reflect_table[REFLECT_HEIGHT];
349 static struct slide_data center_slide;
350 static struct slide_data left_slides[MAX_SLIDES_COUNT];
351 static struct slide_data right_slides[MAX_SLIDES_COUNT];
352 static int slide_frame;
353 static int step;
354 static int target;
355 static int fade;
356 static int center_index = 0; /* index of the slide that is in the center */
357 static int itilt;
358 static PFreal offsetX;
359 static PFreal offsetY;
360 static int number_of_slides;
362 static struct slide_cache cache[SLIDE_CACHE_SIZE];
363 static int cache_free;
364 static int cache_used = -1;
365 static int cache_left_index = -1;
366 static int cache_right_index = -1;
367 static int cache_center_index = -1;
369 /* use long for aligning */
370 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
371 /* queue (as array) for scheduling load_surface */
373 static int empty_slide_hid;
375 unsigned int thread_id;
376 struct event_queue thread_q;
378 static struct tagcache_search tcs;
380 static struct buflib_context buf_ctx;
382 static struct album_data *album;
383 static char *album_names;
384 static int album_count;
386 static struct track_data *tracks;
387 static char *track_names;
388 static size_t borrowed = 0;
389 static int track_count;
390 static int track_index;
391 static int selected_track;
392 static int selected_track_pulse;
393 void reset_track_list(void);
395 void * buf;
396 size_t buf_size;
398 static bool thread_is_running;
400 static int cover_animation_keyframe;
401 static int extra_fade;
403 static int albumtxt_x = 0;
404 static int albumtxt_dir = -1;
405 static int prev_center_index = -1;
407 static int start_index_track_list = 0;
408 static int track_list_visible_entries = 0;
409 static int track_list_y;
410 static int track_list_h;
411 static int track_scroll_index = 0;
412 static int track_scroll_dir = 1;
415 Proposals for transitions:
417 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
418 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
420 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
422 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
424 TODO:
425 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
426 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
428 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
430 enum pf_states {
431 pf_idle = 0,
432 pf_scrolling,
433 pf_cover_in,
434 pf_show_tracks,
435 pf_cover_out
438 static int pf_state;
440 /** code */
441 static bool free_slide_prio(int prio);
442 static inline unsigned fade_color(pix_t c, unsigned a);
443 bool save_pfraw(char* filename, struct bitmap *bm);
444 bool load_new_slide(void);
445 int load_surface(int);
447 static inline PFreal fmul(PFreal a, PFreal b)
449 return (a*b) >> PFREAL_SHIFT;
453 * This version preshifts each operand, which is useful when we know how many
454 * of the least significant bits will be empty, or are worried about overflow
455 * in a particular calculation
457 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
459 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
462 /* ARMv5+ has a clz instruction equivalent to our function.
464 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
465 static inline int clz(uint32_t v)
467 return __builtin_clz(v);
470 /* Otherwise, use our clz, which can be inlined */
471 #elif defined(CPU_COLDFIRE)
472 /* This clz is based on the log2(n) implementation at
473 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
474 * A clz benchmark plugin showed this to be about 14% faster on coldfire
475 * than the LUT-based version.
477 static inline int clz(uint32_t v)
479 int r = 32;
480 if (v >= 0x10000)
482 v >>= 16;
483 r -= 16;
485 if (v & 0xff00)
487 v >>= 8;
488 r -= 8;
490 if (v & 0xf0)
492 v >>= 4;
493 r -= 4;
495 if (v & 0xc)
497 v >>= 2;
498 r -= 2;
500 if (v & 2)
502 v >>= 1;
503 r -= 1;
505 r -= v;
506 return r;
508 #else
509 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
510 0, 0, 0, 0, 0, 0, 0, 0 };
511 /* This clz is based on the log2(n) implementation at
512 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
513 * It is not any faster than the one above, but trades 16B in the lookup table
514 * for a savings of 12B per each inlined call.
516 static inline int clz(uint32_t v)
518 int r = 28;
519 if (v >= 0x10000)
521 v >>= 16;
522 r -= 16;
524 if (v & 0xff00)
526 v >>= 8;
527 r -= 8;
529 if (v & 0xf0)
531 v >>= 4;
532 r -= 4;
534 return r + clz_lut[v];
536 #endif
538 /* Return the maximum possible left shift for a signed int32, without
539 * overflow
541 static inline int allowed_shift(int32_t val)
543 uint32_t uval = val ^ (val >> 31);
544 return clz(uval) - 1;
547 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
548 * num and den before dividing.
550 static inline PFreal fdiv(PFreal num, PFreal den)
552 int shift = allowed_shift(num);
553 shift = MIN(PFREAL_SHIFT, shift);
554 num <<= shift;
555 den >>= PFREAL_SHIFT - shift;
556 return num / den;
559 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
560 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
561 #define fabs(a) (a < 0 ? -a : a)
562 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
564 #if CONFIG_CPU == SH7034
565 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
566 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
567 #else
568 #define MULUQ(a, b) ((a) * (b))
569 #endif
572 #if 0
573 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
574 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
576 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
577 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
579 static inline PFreal fmul(PFreal a, PFreal b)
581 return (a*b) >> PFREAL_SHIFT;
584 static inline PFreal fdiv(PFreal n, PFreal m)
586 return (n<<(PFREAL_SHIFT))/m;
588 #endif
590 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
591 static const short sin_tab[] = {
592 0, 100, 200, 297, 392, 483, 569, 650,
593 724, 792, 851, 903, 946, 980, 1004, 1019,
594 1024, 1019, 1004, 980, 946, 903, 851, 792,
595 724, 650, 569, 483, 392, 297, 200, 100,
596 0, -100, -200, -297, -392, -483, -569, -650,
597 -724, -792, -851, -903, -946, -980, -1004, -1019,
598 -1024, -1019, -1004, -980, -946, -903, -851, -792,
599 -724, -650, -569, -483, -392, -297, -200, -100,
603 static inline PFreal fsin(int iangle)
605 iangle &= IANGLE_MASK;
607 int i = (iangle >> 4);
608 PFreal p = sin_tab[i];
609 PFreal q = sin_tab[(i+1)];
610 PFreal g = (q - p);
611 return p + g * (iangle-i*16)/16;
614 static inline PFreal fcos(int iangle)
616 return fsin(iangle + (IANGLE_MAX >> 2));
619 static inline unsigned scale_val(unsigned val, unsigned bits)
621 val = val * ((1 << bits) - 1);
622 return ((val >> 8) + val + 128) >> 8;
625 static void output_row_8_transposed(uint32_t row, void * row_in,
626 struct scaler_context *ctx)
628 pix_t *dest = (pix_t*)ctx->bm->data + row;
629 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
630 #ifdef USEGSLIB
631 uint8_t *qp = (uint8_t*)row_in;
632 for (; dest < end; dest += ctx->bm->height)
633 *dest = *qp++;
634 #else
635 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
636 unsigned r, g, b;
637 for (; dest < end; dest += ctx->bm->height)
639 r = scale_val(qp->red, 5);
640 g = scale_val(qp->green, 6);
641 b = scale_val((qp++)->blue, 5);
642 *dest = LCD_RGBPACK_LCD(r,g,b);
644 #endif
647 static void output_row_32_transposed(uint32_t row, void * row_in,
648 struct scaler_context *ctx)
650 pix_t *dest = (pix_t*)ctx->bm->data + row;
651 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
652 #ifdef USEGSLIB
653 uint32_t *qp = (uint32_t*)row_in;
654 for (; dest < end; dest += ctx->bm->height)
655 *dest = SC_OUT(*qp++, ctx);
656 #else
657 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
658 int r, g, b;
659 for (; dest < end; dest += ctx->bm->height)
661 r = scale_val(SC_OUT(qp->r, ctx), 5);
662 g = scale_val(SC_OUT(qp->g, ctx), 6);
663 b = scale_val(SC_OUT(qp->b, ctx), 5);
664 qp++;
665 *dest = LCD_RGBPACK_LCD(r,g,b);
667 #endif
670 #ifdef HAVE_LCD_COLOR
671 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
672 struct scaler_context *ctx)
674 pix_t *dest = (pix_t*)ctx->bm->data + row;
675 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
676 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
677 for (; dest < end; dest += ctx->bm->height)
679 unsigned r, g, b, y, u, v;
680 y = SC_OUT(qp->b, ctx);
681 u = SC_OUT(qp->g, ctx);
682 v = SC_OUT(qp->r, ctx);
683 qp++;
684 yuv_to_rgb(y, u, v, &r, &g, &b);
685 r = scale_val(r, 5);
686 g = scale_val(g, 6);
687 b = scale_val(b, 5);
688 *dest = LCD_RGBPACK_LCD(r, g, b);
691 #endif
693 static unsigned int get_size(struct bitmap *bm)
695 return bm->width * bm->height * sizeof(pix_t);
698 const struct custom_format format_transposed = {
699 .output_row_8 = output_row_8_transposed,
700 #ifdef HAVE_LCD_COLOR
701 .output_row_32 = {
702 output_row_32_transposed,
703 output_row_32_transposed_fromyuv
705 #else
706 .output_row_32 = output_row_32_transposed,
707 #endif
708 .get_size = get_size
711 static const struct button_mapping* get_context_map(int context)
713 return pf_contexts[context & ~CONTEXT_PLUGIN];
716 /* Create the lookup table with the scaling values for the reflections */
717 void init_reflect_table(void)
719 int i;
720 for (i = 0; i < REFLECT_HEIGHT; i++)
721 reflect_table[i] =
722 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
723 (5 * REFLECT_HEIGHT);
727 Create an index of all albums from the database.
728 Also store the album names so we can access them later.
730 int create_album_index(void)
732 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
733 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
734 album_count = 0;
735 rb->tagcache_search(&tcs, tag_album);
736 unsigned int l, old_l = 0;
737 album_names = buf;
738 album[0].name_idx = 0;
739 while (rb->tagcache_get_next(&tcs))
741 buf_size -= sizeof(struct album_data);
742 l = tcs.result_len;
743 if ( album_count > 0 )
744 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
746 if ( l > buf_size )
747 /* not enough memory */
748 return ERROR_BUFFER_FULL;
750 rb->strcpy(buf, tcs.result);
751 buf_size -= l;
752 buf = l + (char *)buf;
753 album[-album_count].seek = tcs.result_seek;
754 old_l = l;
755 album_count++;
757 rb->tagcache_search_finish(&tcs);
758 ALIGN_BUFFER(buf, buf_size, 4);
759 int i;
760 struct album_data* tmp_album = (struct album_data*)buf;
761 for (i = album_count - 1; i >= 0; i--)
762 tmp_album[i] = album[-i];
763 album = tmp_album;
764 buf = album + album_count;
765 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
769 Return a pointer to the album name of the given slide_index
771 char* get_album_name(const int slide_index)
773 return album_names + album[slide_index].name_idx;
777 Return a pointer to the track name of the active album
778 create_track_index has to be called first.
780 char* get_track_name(const int track_index)
782 if ( track_index < track_count )
783 return track_names + tracks[track_index].name_idx;
784 return 0;
786 #if PF_PLAYBACK_CAPABLE
787 char* get_track_filename(const int track_index)
789 if ( track_index < track_count )
790 return track_names + tracks[track_index].filename_idx;
791 return 0;
793 #endif
795 int get_wps_current_index(void)
797 struct mp3entry *id3 = rb->audio_current_track();
798 if(id3 && id3->album) {
799 int i;
800 for( i=0; i < album_count; i++ )
801 if(!rb->strcmp(album_names + album[i].name_idx, id3->album))
802 return i;
804 return last_album;
808 Compare two unsigned ints passed via pointers.
810 int compare_tracks (const void *a_v, const void *b_v)
812 uint32_t a = ((struct track_data *)a_v)->sort;
813 uint32_t b = ((struct track_data *)b_v)->sort;
814 return (int)(a - b);
818 Create the track index of the given slide_index.
820 void create_track_index(const int slide_index)
822 if ( slide_index == track_index )
823 return;
824 track_index = slide_index;
826 if (!rb->tagcache_search(&tcs, tag_title))
827 goto fail;
829 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
830 track_count=0;
831 int string_index = 0, track_num;
832 int disc_num;
833 size_t out = 0;
834 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
835 borrowed += out;
836 int avail = borrowed;
837 tracks = (struct track_data*)(track_names + borrowed);
838 while (rb->tagcache_get_next(&tcs))
840 int len = 0, fn_idx = 0;
842 avail -= sizeof(struct track_data);
843 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
844 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
846 if (disc_num < 0)
847 disc_num = 0;
848 retry:
849 if (track_num >= 0)
851 if (disc_num)
852 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
853 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
854 else
855 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
856 "%d: %s", track_num + 1, tcs.result);
858 else
860 track_num = 0;
861 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
862 "%s", tcs.result);
864 if (fn_idx <= 0)
865 goto fail;
866 #if PF_PLAYBACK_CAPABLE
867 int remain = avail - fn_idx;
868 if (remain >= MAX_PATH)
869 { /* retrieve filename for building the playlist */
870 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
871 track_names + string_index + fn_idx, remain);
872 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
873 /* make sure track name and file name are really split by a \0, else
874 * get_track_name might fail */
875 *(track_names + string_index + fn_idx -1) = '\0';
878 else /* request more buffer so that track and filename fit */
879 len = (avail - remain) + MAX_PATH;
880 #else
881 len = fn_idx;
882 #endif
883 if (len > avail)
885 while (len > avail)
887 if (!free_slide_prio(0))
888 goto fail;
889 out = 0;
890 buflib_buffer_out(&buf_ctx, &out);
891 avail += out;
892 borrowed += out;
894 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
895 unsigned int bytes = track_count * sizeof(struct track_data);
896 if (track_count)
897 rb->memmove(new_tracks, tracks, bytes);
898 tracks = new_tracks;
900 goto retry;
903 avail -= len;
904 tracks--;
905 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
906 tracks->name_idx = string_index;
907 tracks->seek = tcs.result_seek;
908 #if PF_PLAYBACK_CAPABLE
909 tracks->filename_idx = fn_idx + string_index;
910 #endif
911 track_count++;
912 string_index += len;
915 rb->tagcache_search_finish(&tcs);
917 /* now fix the track list order */
918 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
919 return;
920 fail:
921 track_count = 0;
922 return;
926 Determine filename of the album art for the given slide_index and
927 store the result in buf.
928 The algorithm looks for the first track of the given album uses
929 find_albumart to find the filename.
931 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
932 int buflen)
934 if ( slide_index == -1 )
936 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
939 if (!rb->tagcache_search(&tcs, tag_filename))
940 return false;
942 bool result;
943 /* find the first track of the album */
944 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
946 if ( rb->tagcache_get_next(&tcs) ) {
947 struct mp3entry id3;
948 int fd;
950 #ifdef HAVE_TC_RAMCACHE
951 if (rb->tagcache_fill_tags(&id3, tcs.result))
953 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
955 else
956 #endif
958 fd = rb->open(tcs.result, O_RDONLY);
959 rb->get_metadata(&id3, fd, tcs.result);
960 rb->close(fd);
962 if ( search_albumart_files(&id3, ":", buf, buflen) )
963 result = true;
964 else
965 result = false;
967 else {
968 /* did not find a matching track */
969 result = false;
971 rb->tagcache_search_finish(&tcs);
972 return result;
976 Draw the PictureFlow logo
978 void draw_splashscreen(void)
980 unsigned char * buf_tmp = buf;
981 size_t buf_tmp_size = buf_size;
982 struct screen* display = rb->screens[0];
983 #if FB_DATA_SZ > 1
984 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
985 #endif
986 struct bitmap logo = {
987 #if LCD_WIDTH < 200
988 .width = 100,
989 .height = 18,
990 #else
991 .width = 193,
992 .height = 34,
993 #endif
994 .data = buf_tmp
996 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
997 NULL);
998 #if LCD_DEPTH > 1
999 rb->lcd_set_background(N_BRIGHT(0));
1000 rb->lcd_set_foreground(N_BRIGHT(255));
1001 #else
1002 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1003 #endif
1004 rb->lcd_clear_display();
1006 if (ret > 0)
1008 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1009 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
1010 #endif
1011 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
1012 logo.width, logo.height);
1013 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1014 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1015 #endif
1018 rb->lcd_update();
1023 Draw a simple progress bar
1025 void draw_progressbar(int step)
1027 int txt_w, txt_h;
1028 const int bar_height = 22;
1029 const int w = LCD_WIDTH - 20;
1030 const int x = 10;
1032 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1034 int y = (LCD_HEIGHT - txt_h)/2;
1036 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1037 y += (txt_h + 5);
1039 #if LCD_DEPTH > 1
1040 rb->lcd_set_foreground(N_BRIGHT(100));
1041 #endif
1042 rb->lcd_drawrect(x, y, w+2, bar_height);
1043 #if LCD_DEPTH > 1
1044 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1045 #endif
1047 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1048 #if LCD_DEPTH > 1
1049 rb->lcd_set_foreground(N_BRIGHT(255));
1050 #endif
1051 rb->lcd_update();
1052 rb->yield();
1055 /* Calculate modified FNV hash of string
1056 * has good avalanche behaviour and uniform distribution
1057 * see http://home.comcast.net/~bretm/hash/ */
1058 unsigned int mfnv(char *str)
1060 const unsigned int p = 16777619;
1061 unsigned int hash = 0x811C9DC5; // 2166136261;
1063 while(*str)
1064 hash = (hash ^ *str++) * p;
1065 hash += hash << 13;
1066 hash ^= hash >> 7;
1067 hash += hash << 3;
1068 hash ^= hash >> 17;
1069 hash += hash << 5;
1070 return hash;
1074 Precomupte the album art images and store them in CACHE_PREFIX.
1076 bool create_albumart_cache(void)
1078 int ret;
1080 int i, slides = 0;
1081 struct bitmap input_bmp;
1083 char pfraw_file[MAX_PATH];
1084 char albumart_file[MAX_PATH];
1085 unsigned int format = FORMAT_NATIVE;
1086 bool forced = cache_version == 0;
1087 cache_version = 0;
1088 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1089 if (resize)
1090 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1091 for (i=0; i < album_count; i++)
1093 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1094 mfnv(get_album_name(i)));
1095 /* delete existing cache, so it's a true rebuild */
1096 if(rb->file_exists(pfraw_file)) {
1097 if(!forced)
1098 continue;
1099 rb->remove(pfraw_file);
1101 draw_progressbar(i);
1102 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1103 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1105 input_bmp.data = buf;
1106 input_bmp.width = DISPLAY_WIDTH;
1107 input_bmp.height = DISPLAY_HEIGHT;
1108 ret = read_image_file(albumart_file, &input_bmp, buf_size, format, &format_transposed);
1109 if (ret <= 0) {
1110 rb->splashf(HZ, "Album art is bad: %s", get_album_name(i));
1111 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1112 ret = read_image_file(albumart_file, &input_bmp, buf_size, format, &format_transposed);
1113 if(ret <= 0)
1114 continue;
1116 if (!save_pfraw(pfraw_file, &input_bmp))
1118 rb->splash(HZ, "Could not write bmp");
1120 slides++;
1121 if ( rb->button_get(false) == PF_MENU ) return false;
1123 if ( slides == 0 ) {
1124 /* Warn the user that we couldn't find any albumart */
1125 rb->splash(2*HZ, "No album art found");
1126 return false;
1128 return true;
1132 Thread used for loading and preparing bitmaps in the background
1134 void thread(void)
1136 long sleep_time = 5 * HZ;
1137 struct queue_event ev;
1138 while (1) {
1139 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1140 switch (ev.id) {
1141 case EV_EXIT:
1142 return;
1143 case EV_WAKEUP:
1144 /* we just woke up */
1145 break;
1147 if(ev.id != SYS_TIMEOUT)
1148 while ( load_new_slide() ) {
1149 rb->yield();
1150 switch (ev.id) {
1151 case EV_EXIT:
1152 return;
1160 End the thread by posting the EV_EXIT event
1162 void end_pf_thread(void)
1164 if ( thread_is_running ) {
1165 rb->queue_post(&thread_q, EV_EXIT, 0);
1166 rb->thread_wait(thread_id);
1167 /* remove the thread's queue from the broadcast list */
1168 rb->queue_delete(&thread_q);
1169 thread_is_running = false;
1176 Create the thread an setup the event queue
1178 bool create_pf_thread(void)
1180 /* put the thread's queue in the bcast list */
1181 rb->queue_init(&thread_q, true);
1182 if ((thread_id = rb->create_thread(
1183 thread,
1184 thread_stack,
1185 sizeof(thread_stack),
1187 "Picture load thread"
1188 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1189 PRIORITY_REALTIME + 1))
1190 IF_COP(, CPU)
1192 ) == 0) {
1193 return false;
1195 thread_is_running = true;
1196 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1197 return true;
1201 Safe the given bitmap as filename in the pfraw format
1203 bool save_pfraw(char* filename, struct bitmap *bm)
1205 struct pfraw_header bmph;
1206 bmph.width = bm->width;
1207 bmph.height = bm->height;
1208 int fh = rb->creat( filename , 0666);
1209 if( fh < 0 ) return false;
1210 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1211 int y;
1212 for( y = 0; y < bm->height; y++ )
1214 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1215 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1217 rb->close( fh );
1218 return true;
1223 * The following functions implement the linked-list-in-array used to manage
1224 * the LRU cache of slides, and the list of free cache slots.
1227 #define seek_right_while(start, cond) \
1228 ({ \
1229 int ind_, next_ = (start); \
1230 do { \
1231 ind_ = next_; \
1232 next_ = cache[ind_].next; \
1233 } while (next_ != cache_used && (cond)); \
1234 ind_; \
1237 #define seek_left_while(start, cond) \
1238 ({ \
1239 int ind_, next_ = (start); \
1240 do { \
1241 ind_ = next_; \
1242 next_ = cache[ind_].prev; \
1243 } while (ind_ != cache_used && (cond)); \
1244 ind_; \
1248 Pop the given item from the linked list starting at *head, returning the next
1249 item, or -1 if the list is now empty.
1251 static inline int lla_pop_item (int *head, int i)
1253 int prev = cache[i].prev;
1254 int next = cache[i].next;
1255 if (i == next)
1257 *head = -1;
1258 return -1;
1260 else if (i == *head)
1261 *head = next;
1262 cache[next].prev = prev;
1263 cache[prev].next = next;
1264 return next;
1269 Pop the head item from the list starting at *head, returning the index of the
1270 item, or -1 if the list is already empty.
1272 static inline int lla_pop_head (int *head)
1274 int i = *head;
1275 if (i != -1)
1276 lla_pop_item(head, i);
1277 return i;
1281 Insert the item at index i before the one at index p.
1283 static inline void lla_insert (int i, int p)
1285 int next = p;
1286 int prev = cache[next].prev;
1287 cache[next].prev = i;
1288 cache[prev].next = i;
1289 cache[i].next = next;
1290 cache[i].prev = prev;
1295 Insert the item at index i at the end of the list starting at *head.
1297 static inline void lla_insert_tail (int *head, int i)
1299 if (*head == -1)
1301 *head = i;
1302 cache[i].next = i;
1303 cache[i].prev = i;
1304 } else
1305 lla_insert(i, *head);
1309 Insert the item at index i before the one at index p.
1311 static inline void lla_insert_after(int i, int p)
1313 p = cache[p].next;
1314 lla_insert(i, p);
1319 Insert the item at index i before the one at index p in the list starting at
1320 *head
1322 static inline void lla_insert_before(int *head, int i, int p)
1324 lla_insert(i, p);
1325 if (*head == p)
1326 *head = i;
1331 Free the used slide at index i, and its buffer, and move it to the free
1332 slides list.
1334 static inline void free_slide(int i)
1336 if (cache[i].hid != empty_slide_hid)
1337 buflib_free(&buf_ctx, cache[i].hid);
1338 cache[i].index = -1;
1339 lla_pop_item(&cache_used, i);
1340 lla_insert_tail(&cache_free, i);
1341 if (cache_used == -1)
1343 cache_right_index = -1;
1344 cache_left_index = -1;
1345 cache_center_index = -1;
1351 Free one slide ranked above the given priority. If no such slide can be found,
1352 return false.
1354 static bool free_slide_prio(int prio)
1356 if (cache_used == -1)
1357 return false;
1358 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1359 int prio_l = cache[l].index < center_index ?
1360 center_index - cache[l].index : 0;
1361 int prio_r = cache[r].index > center_index ?
1362 cache[r].index - center_index : 0;
1363 if (prio_l > prio_r)
1365 i = l;
1366 prio_max = prio_l;
1367 } else {
1368 i = r;
1369 prio_max = prio_r;
1371 if (prio_max > prio)
1373 if (i == cache_left_index)
1374 cache_left_index = cache[i].next;
1375 if (i == cache_right_index)
1376 cache_right_index = cache[i].prev;
1377 free_slide(i);
1378 return true;
1379 } else
1380 return false;
1384 Read the pfraw image given as filename and return the hid of the buffer
1386 int read_pfraw(char* filename, int prio)
1388 struct pfraw_header bmph;
1389 int fh = rb->open(filename, O_RDONLY);
1390 if( fh < 0 ) {
1391 cache_version = 1;
1392 return empty_slide_hid;
1394 else
1395 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1397 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1398 bmph.width * bmph.height;
1400 int hid;
1401 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1403 if (!hid) {
1404 rb->close( fh );
1405 return 0;
1408 rb->yield(); // allow audio to play when fast scrolling
1409 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1411 bm->width = bmph.width;
1412 bm->height = bmph.height;
1413 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1415 int y;
1416 for( y = 0; y < bm->height; y++ )
1418 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1419 data += bm->width;
1421 rb->close( fh );
1422 return hid;
1427 Load the surface for the given slide_index into the cache at cache_index.
1429 static inline bool load_and_prepare_surface(const int slide_index,
1430 const int cache_index,
1431 const int prio)
1433 char tmp_path_name[MAX_PATH+1];
1434 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%x.pfraw",
1435 mfnv(get_album_name(slide_index)));
1437 int hid = read_pfraw(tmp_path_name, prio);
1438 if (!hid)
1439 return false;
1441 cache[cache_index].hid = hid;
1443 if ( cache_index < SLIDE_CACHE_SIZE ) {
1444 cache[cache_index].index = slide_index;
1447 return true;
1452 Load the "next" slide that we can load, freeing old slides if needed, provided
1453 that they are further from center_index than the current slide
1455 bool load_new_slide(void)
1457 int i = -1;
1458 if (cache_center_index != -1)
1460 int next, prev;
1461 if (cache[cache_center_index].index != center_index)
1463 if (cache[cache_center_index].index < center_index)
1465 cache_center_index = seek_right_while(cache_center_index,
1466 cache[next_].index <= center_index);
1467 prev = cache_center_index;
1468 next = cache[cache_center_index].next;
1470 else
1472 cache_center_index = seek_left_while(cache_center_index,
1473 cache[next_].index >= center_index);
1474 next = cache_center_index;
1475 prev = cache[cache_center_index].prev;
1477 if (cache[cache_center_index].index != center_index)
1479 if (cache_free == -1)
1480 free_slide_prio(0);
1481 i = lla_pop_head(&cache_free);
1482 if (!load_and_prepare_surface(center_index, i, 0))
1483 goto fail_and_refree;
1484 if (cache[next].index == -1)
1486 if (cache[prev].index == -1)
1487 goto insert_first_slide;
1488 else
1489 next = cache[prev].next;
1491 lla_insert(i, next);
1492 if (cache[i].index < cache[cache_used].index)
1493 cache_used = i;
1494 cache_center_index = i;
1495 cache_left_index = i;
1496 cache_right_index = i;
1497 return true;
1500 if (cache[cache_left_index].index >
1501 cache[cache_center_index].index)
1502 cache_left_index = cache_center_index;
1503 if (cache[cache_right_index].index <
1504 cache[cache_center_index].index)
1505 cache_right_index = cache_center_index;
1506 cache_left_index = seek_left_while(cache_left_index,
1507 cache[ind_].index - 1 == cache[next_].index);
1508 cache_right_index = seek_right_while(cache_right_index,
1509 cache[ind_].index - 1 == cache[next_].index);
1510 int prio_l = cache[cache_center_index].index -
1511 cache[cache_left_index].index + 1;
1512 int prio_r = cache[cache_right_index].index -
1513 cache[cache_center_index].index + 1;
1514 if ((prio_l < prio_r ||
1515 cache[cache_right_index].index >= number_of_slides) &&
1516 cache[cache_left_index].index > 0)
1518 if (cache_free == -1 && !free_slide_prio(prio_l))
1519 return false;
1520 i = lla_pop_head(&cache_free);
1521 if (load_and_prepare_surface(cache[cache_left_index].index
1522 - 1, i, prio_l))
1524 lla_insert_before(&cache_used, i, cache_left_index);
1525 cache_left_index = i;
1526 return true;
1528 } else if(cache[cache_right_index].index < number_of_slides - 1)
1530 if (cache_free == -1 && !free_slide_prio(prio_r))
1531 return false;
1532 i = lla_pop_head(&cache_free);
1533 if (load_and_prepare_surface(cache[cache_right_index].index
1534 + 1, i, prio_r))
1536 lla_insert_after(i, cache_right_index);
1537 cache_right_index = i;
1538 return true;
1541 } else {
1542 i = lla_pop_head(&cache_free);
1543 if (load_and_prepare_surface(center_index, i, 0))
1545 insert_first_slide:
1546 cache[i].next = i;
1547 cache[i].prev = i;
1548 cache_center_index = i;
1549 cache_left_index = i;
1550 cache_right_index = i;
1551 cache_used = i;
1552 return true;
1555 fail_and_refree:
1556 if (i != -1)
1558 lla_insert_tail(&cache_free, i);
1560 return false;
1565 Get a slide from the buffer
1567 static inline struct dim *get_slide(const int hid)
1569 if (!hid)
1570 return NULL;
1572 struct dim *bmp;
1574 bmp = buflib_get_data(&buf_ctx, hid);
1576 return bmp;
1581 Return the requested surface
1583 static inline struct dim *surface(const int slide_index)
1585 if (slide_index < 0)
1586 return 0;
1587 if (slide_index >= number_of_slides)
1588 return 0;
1589 int i;
1590 if ((i = cache_used ) != -1)
1592 do {
1593 if (cache[i].index == slide_index)
1594 return get_slide(cache[i].hid);
1595 i = cache[i].next;
1596 } while (i != cache_used);
1598 return get_slide(empty_slide_hid);
1602 adjust slides so that they are in "steady state" position
1604 void reset_slides(void)
1606 center_slide.angle = 0;
1607 center_slide.cx = 0;
1608 center_slide.cy = 0;
1609 center_slide.distance = 0;
1610 center_slide.slide_index = center_index;
1612 int i;
1613 for (i = 0; i < num_slides; i++) {
1614 struct slide_data *si = &left_slides[i];
1615 si->angle = itilt;
1616 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1617 si->cy = offsetY;
1618 si->slide_index = center_index - 1 - i;
1619 si->distance = 0;
1622 for (i = 0; i < num_slides; i++) {
1623 struct slide_data *si = &right_slides[i];
1624 si->angle = -itilt;
1625 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1626 si->cy = offsetY;
1627 si->slide_index = center_index + 1 + i;
1628 si->distance = 0;
1634 Updates look-up table and other stuff necessary for the rendering.
1635 Call this when the viewport size or slide dimension is changed.
1637 * To calculate the offset that will provide the proper margin, we use the same
1638 * projection used to render the slides. The solution for xc, the slide center,
1639 * is:
1640 * xp * (zo + xs * sin(r))
1641 * xc = xp - xs * cos(r) + ───────────────────────
1643 * TODO: support moving the side slides toward or away from the camera
1645 void recalc_offsets(void)
1647 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1648 PFreal zo;
1649 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1650 PFREAL_ONE) * zoom / 100;
1651 PFreal cosr, sinr;
1653 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1654 cosr = fcos(-itilt);
1655 sinr = fsin(-itilt);
1656 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1657 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1658 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1659 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1660 / CAM_DIST;
1661 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1666 Fade the given color by spreading the fb_data (ushort)
1667 to an uint, multiply and compress the result back to a ushort.
1669 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1670 static inline unsigned fade_color(pix_t c, unsigned a)
1672 unsigned int result;
1673 c = swap16(c);
1674 a = (a + 2) & 0x1fc;
1675 result = ((c & 0xf81f) * a) & 0xf81f00;
1676 result |= ((c & 0x7e0) * a) & 0x7e000;
1677 result >>= 8;
1678 return swap16(result);
1680 #elif LCD_PIXELFORMAT == RGB565
1681 static inline unsigned fade_color(pix_t c, unsigned a)
1683 unsigned int result;
1684 a = (a + 2) & 0x1fc;
1685 result = ((c & 0xf81f) * a) & 0xf81f00;
1686 result |= ((c & 0x7e0) * a) & 0x7e000;
1687 result >>= 8;
1688 return result;
1690 #else
1691 static inline unsigned fade_color(pix_t c, unsigned a)
1693 unsigned val = c;
1694 return MULUQ(val, a) >> 8;
1696 #endif
1699 * Render a single slide
1700 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1701 * on the slide from its center, zo is the slide's depth offset from the plane
1702 * of the display, r is the angle at which the slide is tilted, and xp is the
1703 * point on the display corresponding to xs on the slide, the projection
1704 * formulas are:
1706 * z * (xc + xs * cos(r))
1707 * xp = ──────────────────────
1708 * z + zo + xs * sin(r)
1710 * z * (xc - xp) - xp * zo
1711 * xs = ────────────────────────
1712 * xp * sin(r) - z * cos(r)
1714 * We use the xp projection once, to find the left edge of the slide on the
1715 * display. From there, we use the xs reverse projection to find the horizontal
1716 * offset from the slide center of each column on the screen, until we reach
1717 * the right edge of the slide, or the screen. The reverse projection can be
1718 * optimized by saving the numerator and denominator of the fraction, which can
1719 * then be incremented by (z + zo) and sin(r) respectively.
1721 void render_slide(struct slide_data *slide, const int alpha)
1723 struct dim *bmp = surface(slide->slide_index);
1724 if (!bmp) {
1725 return;
1727 if (slide->angle > 255 || slide->angle < -255)
1728 return;
1729 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1731 const int sw = bmp->width;
1732 const int sh = bmp->height;
1733 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1734 const int w = LCD_WIDTH;
1736 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1738 if (alpha == 256) { /* opaque -> copy table */
1739 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1740 } else { /* precalculate faded table */
1741 int i, lalpha;
1742 for (i = 0; i < REFLECT_HEIGHT; i++) {
1743 lalpha = reflect_table[i];
1744 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1748 PFreal cosr = fcos(slide->angle);
1749 PFreal sinr = fsin(slide->angle);
1750 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1751 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1752 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1753 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1754 (CAM_DIST_R + zo + fmul(xs,sinr)));
1756 /* Since we're finding the screen position of the left edge of the slide,
1757 * we round up.
1759 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1760 >> PFREAL_SHIFT;
1761 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1762 if (xi >= w) {
1763 return;
1765 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1766 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1767 xs = fdiv(xsnum, xsden);
1769 xsnumi = -CAM_DIST_R - zo;
1770 xsdeni = sinr;
1771 int x;
1772 int dy = PFREAL_ONE;
1773 for (x = xi; x < w; x++) {
1774 int column = (xs - slide_left) / PFREAL_ONE;
1775 if (column >= sw)
1776 break;
1777 if (zo || slide->angle)
1778 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1780 const pix_t *ptr = &src[column * bmp->height];
1782 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1783 #define PIXELSTEP_Y 1
1784 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1785 #else
1786 #define PIXELSTEP_Y BUFFER_WIDTH
1787 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1788 #endif
1790 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1791 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1792 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1794 if (alpha == 256) {
1795 while (p >= plim) {
1796 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1797 p -= dy;
1798 pixel -= PIXELSTEP_Y;
1800 } else {
1801 while (p >= plim) {
1802 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1803 p -= dy;
1804 pixel -= PIXELSTEP_Y;
1807 rb->yield(); // allow audio to play when fast scrolling
1808 bmp = surface(slide->slide_index); // resync surface due to yield
1809 ptr = &src[column * bmp->height];
1810 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1811 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1812 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1813 p + (LCD_HEIGHT/2) * dy);
1814 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1816 if (alpha == 256) {
1817 while (p < plim) {
1818 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1819 p += dy;
1820 pixel += PIXELSTEP_Y;
1822 } else {
1823 while (p < plim) {
1824 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1825 p += dy;
1826 pixel += PIXELSTEP_Y;
1829 while (p < plim2) {
1830 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1831 int lalpha = reftab[ty];
1832 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1833 p += dy;
1834 pixel += PIXELSTEP_Y;
1837 if (zo || slide->angle)
1839 xsnum += xsnumi;
1840 xsden += xsdeni;
1841 xs = fdiv(xsnum, xsden);
1842 } else
1843 xs += PFREAL_ONE;
1846 /* let the music play... */
1847 rb->yield();
1848 return;
1852 Jump the the given slide_index
1854 static inline void set_current_slide(const int slide_index)
1856 int old_center_index = center_index;
1857 step = 0;
1858 center_index = fbound(slide_index, 0, number_of_slides - 1);
1859 if (old_center_index != center_index)
1860 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1861 target = center_index;
1862 slide_frame = slide_index << 16;
1863 reset_slides();
1867 Start the animation for changing slides
1869 void start_animation(void)
1871 step = (target < center_slide.slide_index) ? -1 : 1;
1872 pf_state = pf_scrolling;
1876 Go to the previous slide
1878 void show_previous_slide(void)
1880 if (step == 0) {
1881 if (center_index > 0) {
1882 target = center_index - 1;
1883 start_animation();
1885 } else if ( step > 0 ) {
1886 target = center_index;
1887 start_animation();
1888 } else {
1889 target = fmax(0, center_index - 2);
1895 Go to the next slide
1897 void show_next_slide(void)
1899 if (step == 0) {
1900 if (center_index < number_of_slides - 1) {
1901 target = center_index + 1;
1902 start_animation();
1904 } else if ( step < 0 ) {
1905 target = center_index;
1906 start_animation();
1907 } else {
1908 target = fmin(center_index + 2, number_of_slides - 1);
1914 Render the slides. Updates only the offscreen buffer.
1916 void render_all_slides(void)
1918 mylcd_set_background(G_BRIGHT(0));
1919 /* TODO: Optimizes this by e.g. invalidating rects */
1920 mylcd_clear_display();
1922 int nleft = num_slides;
1923 int nright = num_slides;
1925 int index;
1926 if (step == 0) {
1927 /* no animation, boring plain rendering */
1928 for (index = nleft - 2; index >= 0; index--) {
1929 int alpha = (index < nleft - 2) ? 256 : 128;
1930 alpha -= extra_fade;
1931 if (alpha > 0 )
1932 render_slide(&left_slides[index], alpha);
1934 for (index = nright - 2; index >= 0; index--) {
1935 int alpha = (index < nright - 2) ? 256 : 128;
1936 alpha -= extra_fade;
1937 if (alpha > 0 )
1938 render_slide(&right_slides[index], alpha);
1940 } else {
1941 /* the first and last slide must fade in/fade out */
1942 for (index = nleft - 1; index >= 0; index--) {
1943 int alpha = 256;
1944 if (index == nleft - 1)
1945 alpha = (step > 0) ? 0 : 128 - fade / 2;
1946 if (index == nleft - 2)
1947 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1948 if (index == nleft - 3)
1949 alpha = (step > 0) ? 256 - fade / 2 : 256;
1950 render_slide(&left_slides[index], alpha);
1952 for (index = nright - 1; index >= 0; index--) {
1953 int alpha = (index < nright - 2) ? 256 : 128;
1954 if (index == nright - 1)
1955 alpha = (step > 0) ? fade / 2 : 0;
1956 if (index == nright - 2)
1957 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1958 if (index == nright - 3)
1959 alpha = (step > 0) ? 256 : 128 + fade / 2;
1960 render_slide(&right_slides[index], alpha);
1963 render_slide(&center_slide, 256);
1968 Updates the animation effect. Call this periodically from a timer.
1970 void update_scroll_animation(void)
1972 if (step == 0)
1973 return;
1975 int speed = 16384;
1976 int i;
1978 /* deaccelerate when approaching the target */
1979 if (true) {
1980 const int max = 2 * 65536;
1982 int fi = slide_frame;
1983 fi -= (target << 16);
1984 if (fi < 0)
1985 fi = -fi;
1986 fi = fmin(fi, max);
1988 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1989 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1992 slide_frame += speed * step;
1994 int index = slide_frame >> 16;
1995 int pos = slide_frame & 0xffff;
1996 int neg = 65536 - pos;
1997 int tick = (step < 0) ? neg : pos;
1998 PFreal ftick = (tick * PFREAL_ONE) >> 16;
2000 /* the leftmost and rightmost slide must fade away */
2001 fade = pos / 256;
2003 if (step < 0)
2004 index++;
2005 if (center_index != index) {
2006 center_index = index;
2007 rb->queue_post(&thread_q, EV_WAKEUP, 0);
2008 slide_frame = index << 16;
2009 center_slide.slide_index = center_index;
2010 for (i = 0; i < num_slides; i++)
2011 left_slides[i].slide_index = center_index - 1 - i;
2012 for (i = 0; i < num_slides; i++)
2013 right_slides[i].slide_index = center_index + 1 + i;
2016 center_slide.angle = (step * tick * itilt) >> 16;
2017 center_slide.cx = -step * fmul(offsetX, ftick);
2018 center_slide.cy = fmul(offsetY, ftick);
2020 if (center_index == target) {
2021 reset_slides();
2022 pf_state = pf_idle;
2023 step = 0;
2024 fade = 256;
2025 return;
2028 for (i = 0; i < num_slides; i++) {
2029 struct slide_data *si = &left_slides[i];
2030 si->angle = itilt;
2031 si->cx =
2032 -(offsetX + slide_spacing * i * PFREAL_ONE + step
2033 * slide_spacing * ftick);
2034 si->cy = offsetY;
2037 for (i = 0; i < num_slides; i++) {
2038 struct slide_data *si = &right_slides[i];
2039 si->angle = -itilt;
2040 si->cx =
2041 offsetX + slide_spacing * i * PFREAL_ONE - step
2042 * slide_spacing * ftick;
2043 si->cy = offsetY;
2046 if (step > 0) {
2047 PFreal ftick = (neg * PFREAL_ONE) >> 16;
2048 right_slides[0].angle = -(neg * itilt) >> 16;
2049 right_slides[0].cx = fmul(offsetX, ftick);
2050 right_slides[0].cy = fmul(offsetY, ftick);
2051 } else {
2052 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2053 left_slides[0].angle = (pos * itilt) >> 16;
2054 left_slides[0].cx = -fmul(offsetX, ftick);
2055 left_slides[0].cy = fmul(offsetY, ftick);
2058 /* must change direction ? */
2059 if (target < index)
2060 if (step > 0)
2061 step = -1;
2062 if (target > index)
2063 if (step < 0)
2064 step = 1;
2069 Cleanup the plugin
2071 void cleanup(void *parameter)
2073 (void) parameter;
2074 int i;
2075 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2076 rb->cpu_boost(false);
2077 #endif
2078 end_pf_thread();
2079 /* Turn on backlight timeout (revert to settings) */
2080 backlight_use_settings(); /* backlight control in lib/helper.c */
2082 #ifdef USEGSLIB
2083 grey_release();
2084 #endif
2085 FOR_NB_SCREENS(i)
2086 rb->viewportmanager_theme_undo(i, false);
2090 Create the "?" slide, that is shown while loading
2091 or when no cover was found.
2093 int create_empty_slide(bool force)
2095 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2096 struct bitmap input_bmp;
2097 int ret;
2098 input_bmp.width = DISPLAY_WIDTH;
2099 input_bmp.height = DISPLAY_HEIGHT;
2100 #if LCD_DEPTH > 1
2101 input_bmp.format = FORMAT_NATIVE;
2102 #endif
2103 input_bmp.data = (char*)buf;
2104 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2105 buf_size,
2106 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2107 &format_transposed);
2108 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2109 return false;
2112 return true;
2116 Shows the settings menu
2118 int settings_menu(void)
2120 int selection = 0;
2121 bool old_val;
2123 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2124 "Spacing", "Centre margin", "Number of slides", "Zoom",
2125 "Show album title", "Resize Covers", "Rebuild cache",
2126 "WPS Integration", "Backlight");
2128 static const struct opt_items album_name_options[] = {
2129 { "Hide album title", -1 },
2130 { "Show at the bottom", -1 },
2131 { "Show at the top", -1 }
2133 static const struct opt_items wps_options[] = {
2134 { "Off", -1 },
2135 { "Direct", -1 },
2136 { "Via Track list", -1 }
2138 static const struct opt_items backlight_options[] = {
2139 { "Always On", -1 },
2140 { "Normal", -1 },
2143 do {
2144 selection=rb->do_menu(&settings_menu,&selection, NULL, true);
2145 switch(selection) {
2146 case 0:
2147 rb->set_bool("Show FPS", &show_fps);
2148 reset_track_list();
2149 break;
2151 case 1:
2152 rb->set_int("Spacing between slides", "", 1,
2153 &slide_spacing,
2154 NULL, 1, 0, 100, NULL );
2155 recalc_offsets();
2156 reset_slides();
2157 break;
2159 case 2:
2160 rb->set_int("Centre margin", "", 1,
2161 &center_margin,
2162 NULL, 1, 0, 80, NULL );
2163 recalc_offsets();
2164 reset_slides();
2165 break;
2167 case 3:
2168 rb->set_int("Number of slides", "", 1, &num_slides,
2169 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2170 recalc_offsets();
2171 reset_slides();
2172 break;
2174 case 4:
2175 rb->set_int("Zoom", "", 1, &zoom,
2176 NULL, 1, 10, 300, NULL );
2177 recalc_offsets();
2178 reset_slides();
2179 break;
2180 case 5:
2181 rb->set_option("Show album title", &show_album_name,
2182 INT, album_name_options, 3, NULL);
2183 reset_track_list();
2184 recalc_offsets();
2185 reset_slides();
2186 break;
2187 case 6:
2188 old_val = resize;
2189 rb->set_bool("Resize Covers", &resize);
2190 if (old_val == resize) /* changed? */
2191 break;
2192 /* fallthrough if changed, since cache needs to be rebuilt */
2193 case 7:
2194 cache_version = 0;
2195 rb->remove(EMPTY_SLIDE);
2196 rb->splash(HZ, "Cache will be rebuilt on next restart");
2197 break;
2198 case 8:
2199 rb->set_option("WPS Integration", &auto_wps, INT, wps_options, 3, NULL);
2200 break;
2201 case 9:
2202 rb->set_option("Backlight", &backlight_mode, INT, backlight_options, 2, NULL);
2203 break;
2205 case MENU_ATTACHED_USB:
2206 return PLUGIN_USB_CONNECTED;
2208 } while ( selection >= 0 );
2209 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2210 return 0;
2214 Show the main menu
2216 enum {
2217 PF_GOTO_WPS,
2218 #if PF_PLAYBACK_CAPABLE
2219 PF_MENU_CLEAR_PLAYLIST,
2220 PF_MENU_PLAYBACK_CONTROL,
2221 #endif
2222 PF_MENU_SETTINGS,
2223 PF_MENU_RETURN,
2224 PF_MENU_QUIT,
2227 int main_menu(void)
2229 int selection = 0;
2230 int result;
2232 #if LCD_DEPTH > 1
2233 rb->lcd_set_foreground(N_BRIGHT(255));
2234 #endif
2236 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2237 "Go to WPS",
2238 #if PF_PLAYBACK_CAPABLE
2239 "Clear playlist", "Playback Control",
2240 #endif
2241 "Settings", "Return", "Quit");
2242 while (1) {
2243 switch (rb->do_menu(&main_menu,&selection, NULL, true)) {
2244 case PF_GOTO_WPS: /* WPS */
2245 return -2;
2246 #if PF_PLAYBACK_CAPABLE
2247 case PF_MENU_CLEAR_PLAYLIST:
2248 if(rb->playlist_remove_all_tracks(NULL) == 0) {
2249 rb->playlist_create(NULL, NULL);
2250 rb->splash(HZ*2, "Playlist Cleared");
2252 break;
2253 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2254 playback_control(NULL);
2255 break;
2256 #endif
2257 case PF_MENU_SETTINGS:
2258 result = settings_menu();
2259 if ( result != 0 ) return result;
2260 break;
2261 case PF_MENU_RETURN:
2262 return 0;
2263 case PF_MENU_QUIT:
2264 return -1;
2266 case MENU_ATTACHED_USB:
2267 return PLUGIN_USB_CONNECTED;
2269 default:
2270 return 0;
2276 Animation step for zooming into the current cover
2278 void update_cover_in_animation(void)
2280 cover_animation_keyframe++;
2281 if( cover_animation_keyframe < 20 ) {
2282 center_slide.distance-=5;
2283 center_slide.angle+=1;
2284 extra_fade += 13;
2286 else if( cover_animation_keyframe < 35 ) {
2287 center_slide.angle+=16;
2289 else {
2290 cover_animation_keyframe = 0;
2291 pf_state = pf_show_tracks;
2296 Animation step for zooming out the current cover
2298 void update_cover_out_animation(void)
2300 cover_animation_keyframe++;
2301 if( cover_animation_keyframe <= 15 ) {
2302 center_slide.angle-=16;
2304 else if( cover_animation_keyframe < 35 ) {
2305 center_slide.distance+=5;
2306 center_slide.angle-=1;
2307 extra_fade -= 13;
2309 else {
2310 cover_animation_keyframe = 0;
2311 pf_state = pf_idle;
2316 Draw a blue gradient at y with height h
2318 static inline void draw_gradient(int y, int h)
2320 int r, inc, c;
2321 inc = (100 << 8) / h;
2322 c = 0;
2323 selected_track_pulse = (selected_track_pulse+1) % 10;
2324 int c2 = selected_track_pulse - 5;
2325 for (r=0; r<h; r++) {
2326 #ifdef HAVE_LCD_COLOR
2327 mylcd_set_foreground(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2328 c2+250-(c >> 8)));
2329 #else
2330 mylcd_set_foreground(G_BRIGHT(c2+160-(c >> 8)));
2331 #endif
2332 mylcd_hline(0, LCD_WIDTH, r+y);
2333 if ( r > h/2 )
2334 c-=inc;
2335 else
2336 c+=inc;
2341 static void track_list_yh(int char_height)
2343 switch (show_album_name)
2345 case album_name_hide:
2346 track_list_y = (show_fps ? char_height : 0);
2347 track_list_h = LCD_HEIGHT - track_list_y;
2348 break;
2349 case album_name_bottom:
2350 track_list_y = (show_fps ? char_height : 0);
2351 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2352 break;
2353 default: /* case album_name_top */
2354 track_list_y = char_height * 2;
2355 track_list_h = LCD_HEIGHT - track_list_y -
2356 (show_fps ? char_height : 0);
2357 break;
2362 Reset the track list after a album change
2364 void reset_track_list(void)
2366 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2367 track_list_yh(albumtxt_h);
2368 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2369 start_index_track_list = 0;
2370 track_scroll_index = 0;
2371 track_scroll_dir = 1;
2372 selected_track = 0;
2374 /* let the tracklist start more centered
2375 * if the screen isn't filled with tracks */
2376 if (track_count*albumtxt_h < track_list_h)
2378 track_list_h = track_count * albumtxt_h;
2379 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2384 Display the list of tracks
2386 void show_track_list(void)
2388 mylcd_clear_display();
2389 if ( center_slide.slide_index != track_index ) {
2390 create_track_index(center_slide.slide_index);
2391 reset_track_list();
2393 int titletxt_w, titletxt_x, color, titletxt_h;
2394 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2396 int titletxt_y = track_list_y;
2397 int track_i;
2398 track_i = start_index_track_list;
2399 for (;track_i < track_list_visible_entries+start_index_track_list;
2400 track_i++)
2402 mylcd_getstringsize(get_track_name(track_i), &titletxt_w, NULL);
2403 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2404 if ( track_i == selected_track ) {
2405 draw_gradient(titletxt_y, titletxt_h);
2406 mylcd_set_foreground(G_BRIGHT(255));
2407 if (titletxt_w > LCD_WIDTH ) {
2408 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2409 track_scroll_dir = 1;
2410 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2411 track_scroll_index += track_scroll_dir*2;
2412 titletxt_x = track_scroll_index;
2414 mylcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
2416 else {
2417 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2418 mylcd_set_foreground(G_BRIGHT(color));
2419 mylcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
2421 titletxt_y += titletxt_h;
2425 void select_next_track(void)
2427 if ( selected_track < track_count - 1 ) {
2428 selected_track++;
2429 track_scroll_index = 0;
2430 track_scroll_dir = 1;
2431 if (selected_track==(track_list_visible_entries+start_index_track_list))
2432 start_index_track_list++;
2436 void select_prev_track(void)
2438 if (selected_track > 0 ) {
2439 if (selected_track==start_index_track_list) start_index_track_list--;
2440 track_scroll_index = 0;
2441 track_scroll_dir = 1;
2442 selected_track--;
2446 #if PF_PLAYBACK_CAPABLE
2448 * Puts the current tracklist into a newly created playlist and starts playling
2450 void start_playback(bool append)
2452 static int old_playlist = -1, old_shuffle = 0;
2453 int count = 0;
2454 int position = selected_track;
2455 int shuffle = rb->global_settings->playlist_shuffle;
2456 /* reuse existing playlist if possible
2457 * regenerate if shuffle is on or changed, since playlist index and
2458 * selected track are "out of sync" */
2459 if (!shuffle && !append && center_slide.slide_index == old_playlist
2460 && (old_shuffle == shuffle))
2462 goto play;
2464 /* First, replace the current playlist with a new one */
2465 else if (append || (rb->playlist_remove_all_tracks(NULL) == 0
2466 && rb->playlist_create(NULL, NULL) == 0))
2468 do {
2469 rb->yield();
2470 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2471 PLAYLIST_INSERT_LAST, false, true) < 0)
2472 break;
2473 } while(++count < track_count);
2474 rb->playlist_sync(NULL);
2476 else
2477 return;
2479 if (rb->global_settings->playlist_shuffle)
2480 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2481 play:
2482 /* TODO: can we adjust selected_track if !play_selected ?
2483 * if shuffle, we can't predict the playing track easily, and for either
2484 * case the track list doesn't get auto scrolled*/
2485 if(!append)
2486 rb->playlist_start(position, 0);
2487 old_playlist = center_slide.slide_index;
2488 old_shuffle = shuffle;
2490 #endif
2493 Draw the current album name
2495 void draw_album_text(void)
2497 if (0 == show_album_name)
2498 return;
2500 int albumtxt_w, albumtxt_h;
2501 int albumtxt_y = 0;
2503 char *albumtxt;
2504 int c;
2505 /* Draw album text */
2506 if ( pf_state == pf_scrolling ) {
2507 c = ((slide_frame & 0xffff )/ 255);
2508 if (step < 0) c = 255-c;
2509 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2510 albumtxt = get_album_name(center_index+step);
2511 c = (c-128)*2;
2513 else {
2514 albumtxt = get_album_name(center_index);
2515 c = (128-c)*2;
2518 else {
2519 c= 255;
2520 albumtxt = get_album_name(center_index);
2523 mylcd_set_foreground(G_BRIGHT(c));
2524 mylcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
2525 if (center_index != prev_center_index) {
2526 albumtxt_x = 0;
2527 albumtxt_dir = -1;
2528 prev_center_index = center_index;
2531 if (show_album_name == album_name_top)
2532 albumtxt_y = albumtxt_h / 2;
2533 else
2534 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2536 if (albumtxt_w > LCD_WIDTH ) {
2537 mylcd_putsxy(albumtxt_x, albumtxt_y , albumtxt);
2538 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2539 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2540 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2541 albumtxt_x += albumtxt_dir;
2544 else {
2545 mylcd_putsxy((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2552 Display an error message and wait for input.
2554 void error_wait(const char *message)
2556 rb->splashf(0, "%s. Press any button to continue.", message);
2557 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2558 rb->yield();
2559 rb->sleep(2 * HZ);
2563 Main function that also contain the main plasma
2564 algorithm.
2566 int main(void)
2568 int ret;
2570 rb->lcd_setfont(FONT_UI);
2572 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2573 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2574 error_wait("Could not create directory " CACHE_PREFIX);
2575 return PLUGIN_ERROR;
2579 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2580 if(auto_wps == 0)
2581 draw_splashscreen();
2582 if(backlight_mode == 0) {
2583 /* Turn off backlight timeout */
2584 backlight_force_on(); /* backlight control in lib/helper.c */
2587 init_reflect_table();
2589 ALIGN_BUFFER(buf, buf_size, 4);
2590 ret = create_album_index();
2591 if (ret == ERROR_BUFFER_FULL) {
2592 error_wait("Not enough memory for album names");
2593 return PLUGIN_ERROR;
2594 } else if (ret == ERROR_NO_ALBUMS) {
2595 error_wait("No albums found. Please enable database");
2596 return PLUGIN_ERROR;
2599 ALIGN_BUFFER(buf, buf_size, 4);
2600 number_of_slides = album_count;
2601 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2602 error_wait("Could not create album art cache");
2603 return PLUGIN_ERROR;
2606 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2607 error_wait("Could not load the empty slide");
2608 return PLUGIN_ERROR;
2610 cache_version = CACHE_VERSION;
2611 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2614 #ifdef USEGSLIB
2615 long grey_buf_used;
2616 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2617 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2619 error_wait("Greylib init failed!");
2620 return PLUGIN_ERROR;
2622 grey_setfont(FONT_UI);
2623 buf_size -= grey_buf_used;
2624 buf = (void*)(grey_buf_used + (char*)buf);
2625 #endif
2626 buflib_init(&buf_ctx, (void *)buf, buf_size);
2628 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2630 error_wait("Unable to load empty slide image");
2631 return PLUGIN_ERROR;
2634 if (!create_pf_thread()) {
2635 error_wait("Cannot create thread!");
2636 return PLUGIN_ERROR;
2639 int i;
2641 /* initialize */
2642 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2643 cache[i].hid = 0;
2644 cache[i].index = 0;
2645 cache[i].next = i + 1;
2646 cache[i].prev = i - 1;
2648 cache[0].prev = i - 1;
2649 cache[i - 1].next = 0;
2650 cache_free = 0;
2651 buffer = LCD_BUF;
2653 pf_state = pf_idle;
2655 track_index = -1;
2656 extra_fade = 0;
2657 slide_frame = 0;
2658 step = 0;
2659 target = 0;
2660 fade = 256;
2662 recalc_offsets();
2663 reset_slides();
2664 set_current_slide(get_wps_current_index());
2666 char fpstxt[10];
2667 int button;
2669 int frames = 0;
2670 long last_update = *rb->current_tick;
2671 long current_update;
2672 long update_interval = 100;
2673 int fps = 0;
2674 int fpstxt_y;
2676 bool instant_update;
2677 #ifdef USEGSLIB
2678 grey_show(true);
2679 grey_set_drawmode(DRMODE_FG);
2680 #endif
2681 rb->lcd_set_drawmode(DRMODE_FG);
2682 while (true) {
2683 current_update = *rb->current_tick;
2684 frames++;
2686 /* Initial rendering */
2687 instant_update = false;
2689 /* Handle states */
2690 switch ( pf_state ) {
2691 case pf_scrolling:
2692 update_scroll_animation();
2693 render_all_slides();
2694 instant_update = true;
2695 break;
2696 case pf_cover_in:
2697 update_cover_in_animation();
2698 render_all_slides();
2699 instant_update = true;
2700 break;
2701 case pf_cover_out:
2702 update_cover_out_animation();
2703 render_all_slides();
2704 instant_update = true;
2705 break;
2706 case pf_show_tracks:
2707 show_track_list();
2708 break;
2709 case pf_idle:
2710 render_all_slides();
2711 break;
2714 /* Calculate FPS */
2715 if (current_update - last_update > update_interval) {
2716 fps = frames * HZ / (current_update - last_update);
2717 last_update = current_update;
2718 frames = 0;
2720 /* Draw FPS */
2721 if (show_fps)
2723 #ifdef USEGSLIB
2724 mylcd_set_foreground(G_BRIGHT(255));
2725 #else
2726 mylcd_set_foreground(G_PIX(255,0,0));
2727 #endif
2728 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2729 if (show_album_name == album_name_top)
2730 fpstxt_y = LCD_HEIGHT -
2731 rb->screens[SCREEN_MAIN]->getcharheight();
2732 else
2733 fpstxt_y = 0;
2734 mylcd_putsxy(0, fpstxt_y, fpstxt);
2736 draw_album_text();
2739 /* Copy offscreen buffer to LCD and give time to other threads */
2740 mylcd_update();
2741 rb->yield();
2743 /*/ Handle buttons */
2744 button = rb->get_custom_action(CONTEXT_PLUGIN
2745 #ifndef USE_CORE_PREVNEXT
2746 |(pf_state == pf_show_tracks ? 1 : 0)
2747 #endif
2748 ,instant_update ? 0 : HZ/16,
2749 get_context_map);
2751 switch (button) {
2752 case PF_QUIT:
2753 return PLUGIN_OK;
2754 case PF_WPS:
2755 return PLUGIN_GOTO_WPS;
2756 case PF_BACK:
2757 if ( pf_state == pf_show_tracks )
2759 buflib_buffer_in(&buf_ctx, borrowed);
2760 borrowed = 0;
2761 track_index = -1;
2762 pf_state = pf_cover_out;
2764 if (pf_state == pf_idle || pf_state == pf_scrolling)
2765 return PLUGIN_OK;
2766 break;
2767 case PF_MENU:
2768 #ifdef USEGSLIB
2769 grey_show(false);
2770 #endif
2771 ret = main_menu();
2772 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2773 if ( ret == -1 ) return PLUGIN_OK;
2774 if ( ret != 0 ) return ret;
2775 #ifdef USEGSLIB
2776 grey_show(true);
2777 #endif
2778 mylcd_set_drawmode(DRMODE_FG);
2779 break;
2781 case PF_NEXT:
2782 case PF_NEXT_REPEAT:
2783 if ( pf_state == pf_show_tracks )
2784 select_next_track();
2785 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2786 show_next_slide();
2787 break;
2789 case PF_PREV:
2790 case PF_PREV_REPEAT:
2791 if ( pf_state == pf_show_tracks )
2792 select_prev_track();
2793 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2794 show_previous_slide();
2795 break;
2796 #if PF_PLAYBACK_CAPABLE
2797 case PF_CONTEXT:
2798 if ( auto_wps != 0 ) {
2799 if( pf_state == pf_idle ) {
2800 create_track_index(center_slide.slide_index);
2801 reset_track_list();
2802 start_playback(true);
2803 rb->splash(HZ*2, "Added to playlist");
2805 else if( pf_state == pf_show_tracks ) {
2806 rb->playlist_insert_track(NULL, get_track_filename(selected_track),
2807 PLAYLIST_INSERT_LAST, false, true);
2808 rb->playlist_sync(NULL);
2809 rb->splash(HZ*2, "Added to playlist");
2812 break;
2813 #endif
2814 case PF_TRACKLIST:
2815 if ( auto_wps == 1 && pf_state == pf_idle ) {
2816 pf_state = pf_cover_in;
2817 break;
2819 case PF_SELECT:
2820 if ( pf_state == pf_idle ) {
2821 #if PF_PLAYBACK_CAPABLE
2822 if(auto_wps == 1) {
2823 create_track_index(center_slide.slide_index);
2824 reset_track_list();
2825 start_playback(false);
2826 last_album = center_index;
2827 return PLUGIN_GOTO_WPS;
2829 else
2830 #endif
2831 pf_state = pf_cover_in;
2833 else if ( pf_state == pf_show_tracks ) {
2834 #if PF_PLAYBACK_CAPABLE
2835 start_playback(false);
2836 if(auto_wps != 0) {
2837 last_album = center_index;
2838 return PLUGIN_GOTO_WPS;
2840 #endif
2842 break;
2843 default:
2844 if (rb->default_event_handler_ex(button, cleanup, NULL)
2845 == SYS_USB_CONNECTED)
2846 return PLUGIN_USB_CONNECTED;
2847 break;
2852 /*************************** Plugin entry point ****************************/
2854 enum plugin_status plugin_start(const void *parameter)
2856 int ret, i;
2857 (void) parameter;
2859 FOR_NB_SCREENS(i)
2860 rb->viewportmanager_theme_enable(i, false, NULL);
2861 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2862 rb->cpu_boost(true);
2863 #endif
2864 #if PF_PLAYBACK_CAPABLE
2865 buf = rb->plugin_get_buffer(&buf_size);
2866 #else
2867 buf = rb->plugin_get_audio_buffer(&buf_size);
2868 #ifndef SIMULATOR
2869 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2871 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2872 buf_size = MIN(buf_size, tmp_size);
2874 #endif
2875 #endif
2876 ret = main();
2877 if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
2878 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2879 CONFIG_VERSION))
2881 rb->splash(HZ, "Error writing config.");
2882 ret = PLUGIN_ERROR;
2886 cleanup(NULL);
2887 return ret;