Accept the last patch FS#10797 with a few changes by me (fixing side effects and...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobb3f81444bb1be23214dff75862e30ac3375b035f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
26 #include "plugin.h"
27 #include <albumart.h>
28 #include "lib/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/helper.h"
31 #include "lib/configfile.h"
32 #include "lib/grey.h"
33 #include "lib/feature_wrappers.h"
34 #include "lib/buflib.h"
36 PLUGIN_HEADER
38 /******************************* Globals ***********************************/
41 * Targets which use plugin_get_audio_buffer() can't have playback from
42 * within pictureflow itself, as the whole core audio buffer is occupied */
43 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
45 #if PF_PLAYBACK_CAPABLE
46 #include "lib/playback_control.h"
47 #endif
49 #define PF_PREV ACTION_STD_PREV
50 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
51 #define PF_NEXT ACTION_STD_NEXT
52 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
53 #define PF_SELECT ACTION_STD_OK
54 #define PF_CONTEXT ACTION_STD_CONTEXT
55 #define PF_BACK ACTION_STD_CANCEL
56 #define PF_MENU ACTION_STD_MENU
57 #define PF_WPS ACTION_TREE_WPS
59 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
61 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
62 CONFIG_KEYPAD == SAMSUNG_YH_PAD
63 #define USE_CORE_PREVNEXT
64 #endif
66 #ifndef USE_CORE_PREVNEXT
67 /* scrollwheel targets use the wheel, just as they do in lists,
68 * so there's no need for a special context,
69 * others use left/right here too (as oppsed to up/down in lists) */
70 const struct button_mapping pf_context_album_scroll[] =
72 #ifdef HAVE_TOUCHSCREEN
73 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
74 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
75 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
76 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
77 #endif
78 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
79 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
80 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
81 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
82 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
83 #else
84 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
85 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
86 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
87 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
88 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
89 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
90 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
91 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
92 #endif
93 #if CONFIG_KEYPAD == ONDIO_PAD
94 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
95 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
96 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
97 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
98 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
99 #endif
100 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
102 #endif /* !USE_CORE_PREVNEXT */
104 const struct button_mapping pf_context_buttons[] =
106 #ifdef HAVE_TOUCHSCREEN
107 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
108 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
109 #endif
110 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
111 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
112 #elif CONFIG_KEYPAD == SANSA_C100_PAD
113 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
114 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
115 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
116 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
117 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
118 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
119 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
120 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
121 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
122 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
124 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
125 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
126 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
127 #if CONFIG_KEYPAD == COWON_D2_PAD
128 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
129 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
130 #endif
131 #elif CONFIG_KEYPAD == SANSA_E200_PAD
132 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
133 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
134 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
135 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
136 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
137 || (CONFIG_KEYPAD == IPOD_4G_PAD)
138 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
139 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
140 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
141 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
142 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
143 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
144 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
145 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
146 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
147 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
148 #endif
149 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
150 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
151 #else
152 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
153 #endif
155 const struct button_mapping *pf_contexts[] =
157 #ifndef USE_CORE_PREVNEXT
158 pf_context_album_scroll,
159 #endif
160 pf_context_buttons
163 #if LCD_DEPTH < 8
164 #if LCD_DEPTH > 1
165 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
166 #else /* LCD_DEPTH <= 1 */
167 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
168 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
169 #define PICTUREFLOW_DRMODE DRMODE_SOLID
170 #else
171 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
172 #endif
173 #endif /* LCD_DEPTH <= 1 */
174 #define USEGSLIB
175 GREY_INFO_STRUCT
176 #define LCD_BUF _grey_info.buffer
177 #define MYLCD(fn) grey_ ## fn
178 #define G_PIX(r,g,b) \
179 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
180 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
181 #define G_BRIGHT(y) (y)
182 #define BUFFER_WIDTH _grey_info.width
183 #define BUFFER_HEIGHT _grey_info.height
184 typedef unsigned char pix_t;
185 #else /* LCD_DEPTH >= 8 */
186 #define LCD_BUF rb->lcd_framebuffer
187 #define MYLCD(fn) rb->lcd_ ## fn
188 #define G_PIX LCD_RGBPACK
189 #define N_PIX LCD_RGBPACK
190 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
191 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
192 #define BUFFER_WIDTH LCD_WIDTH
193 #define BUFFER_HEIGHT LCD_HEIGHT
194 typedef fb_data pix_t;
195 #endif /* LCD_DEPTH >= 8 */
197 /* for fixed-point arithmetic, we need minimum 32-bit long
198 long long (64-bit) might be useful for multiplication and division */
199 #define PFreal long
200 #define PFREAL_SHIFT 10
201 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
202 #define PFREAL_ONE (1 << PFREAL_SHIFT)
203 #define PFREAL_HALF (PFREAL_ONE >> 1)
206 #define IANGLE_MAX 1024
207 #define IANGLE_MASK 1023
209 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
210 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
211 #define DISPLAY_HEIGHT REFLECT_TOP
212 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
213 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
214 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
215 (REFLECT_HEIGHT * 5))
216 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
217 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
218 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
219 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
220 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
222 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
224 #define MAX_SLIDES_COUNT 10
226 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
227 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
229 #define EV_EXIT 9999
230 #define EV_WAKEUP 1337
232 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
233 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
234 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
236 /* Error return values */
237 #define ERROR_NO_ALBUMS -1
238 #define ERROR_BUFFER_FULL -2
240 /* current version for cover cache */
241 #define CACHE_VERSION 3
242 #define CONFIG_VERSION 1
243 #define CONFIG_FILE "pictureflow.cfg"
245 /** structs we use */
247 struct slide_data {
248 int slide_index;
249 int angle;
250 PFreal cx;
251 PFreal cy;
252 PFreal distance;
255 struct slide_cache {
256 int index; /* index of the cached slide */
257 int hid; /* handle ID of the cached slide */
258 short next; /* "next" slide, with LRU last */
259 short prev; /* "previous" slide */
262 struct album_data {
263 int name_idx;
264 long seek;
267 struct track_data {
268 uint32_t sort;
269 int name_idx; /* offset to the track name */
270 long seek;
271 #if PF_PLAYBACK_CAPABLE
272 /* offset to the filename in the string, needed for playlist generation */
273 int filename_idx;
274 #endif
277 struct rect {
278 int left;
279 int right;
280 int top;
281 int bottom;
284 struct load_slide_event_data {
285 int slide_index;
286 int cache_index;
290 struct pfraw_header {
291 int32_t width; /* bmap width in pixels */
292 int32_t height; /* bmap height in pixels */
295 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
296 album_name_top };
297 static char* show_album_name_conf[] =
299 "hide",
300 "bottom",
301 "top"
304 #define MAX_SPACING 40
305 #define MAX_MARGIN 80
307 /* config values and their defaults */
308 static int slide_spacing = DISPLAY_WIDTH / 4;
309 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
310 static int num_slides = 4;
311 static int zoom = 100;
312 static bool show_fps = false;
313 static bool resize = true;
314 static int cache_version = 0;
315 static int show_album_name = (LCD_HEIGHT > 100)
316 ? album_name_top : album_name_bottom;
318 static struct configdata config[] =
320 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
321 NULL },
322 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
323 NULL },
324 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
325 NULL },
326 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
327 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
328 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
329 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
330 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
331 show_album_name_conf }
334 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
336 /** below we allocate the memory we want to use **/
338 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
339 static uint8_t reflect_table[REFLECT_HEIGHT];
340 static struct slide_data center_slide;
341 static struct slide_data left_slides[MAX_SLIDES_COUNT];
342 static struct slide_data right_slides[MAX_SLIDES_COUNT];
343 static int slide_frame;
344 static int step;
345 static int target;
346 static int fade;
347 static int center_index = 0; /* index of the slide that is in the center */
348 static int itilt;
349 static PFreal offsetX;
350 static PFreal offsetY;
351 static int number_of_slides;
353 static struct slide_cache cache[SLIDE_CACHE_SIZE];
354 static int cache_free;
355 static int cache_used = -1;
356 static int cache_left_index = -1;
357 static int cache_right_index = -1;
358 static int cache_center_index = -1;
360 /* use long for aligning */
361 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
362 /* queue (as array) for scheduling load_surface */
364 static int empty_slide_hid;
366 unsigned int thread_id;
367 struct event_queue thread_q;
369 static struct tagcache_search tcs;
371 static struct buflib_context buf_ctx;
373 static struct album_data *album;
374 static char *album_names;
375 static int album_count;
377 static struct track_data *tracks;
378 static char *track_names;
379 static size_t borrowed = 0;
380 static int track_count;
381 static int track_index;
382 static int selected_track;
383 static int selected_track_pulse;
384 void reset_track_list(void);
386 void * buf;
387 size_t buf_size;
389 static bool thread_is_running;
391 static int cover_animation_keyframe;
392 static int extra_fade;
394 static int albumtxt_x = 0;
395 static int albumtxt_dir = -1;
396 static int prev_center_index = -1;
398 static int start_index_track_list = 0;
399 static int track_list_visible_entries = 0;
400 static int track_list_y;
401 static int track_list_h;
402 static int track_scroll_index = 0;
403 static int track_scroll_dir = 1;
406 Proposals for transitions:
408 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
409 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
411 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
413 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
415 TODO:
416 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
417 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
419 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
421 enum pf_states {
422 pf_idle = 0,
423 pf_scrolling,
424 pf_cover_in,
425 pf_show_tracks,
426 pf_cover_out
429 static int pf_state;
431 /** code */
432 static bool free_slide_prio(int prio);
433 static inline unsigned fade_color(pix_t c, unsigned a);
434 bool save_pfraw(char* filename, struct bitmap *bm);
435 bool load_new_slide(void);
436 int load_surface(int);
438 static inline PFreal fmul(PFreal a, PFreal b)
440 return (a*b) >> PFREAL_SHIFT;
444 * This version preshifts each operand, which is useful when we know how many
445 * of the least significant bits will be empty, or are worried about overflow
446 * in a particular calculation
448 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
450 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
453 /* ARMv5+ has a clz instruction equivalent to our function.
455 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
456 static inline int clz(uint32_t v)
458 return __builtin_clz(v);
461 /* Otherwise, use our clz, which can be inlined */
462 #elif defined(CPU_COLDFIRE)
463 /* This clz is based on the log2(n) implementation at
464 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
465 * A clz benchmark plugin showed this to be about 14% faster on coldfire
466 * than the LUT-based version.
468 static inline int clz(uint32_t v)
470 int r = 32;
471 if (v >= 0x10000)
473 v >>= 16;
474 r -= 16;
476 if (v & 0xff00)
478 v >>= 8;
479 r -= 8;
481 if (v & 0xf0)
483 v >>= 4;
484 r -= 4;
486 if (v & 0xc)
488 v >>= 2;
489 r -= 2;
491 if (v & 2)
493 v >>= 1;
494 r -= 1;
496 r -= v;
497 return r;
499 #else
500 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
501 0, 0, 0, 0, 0, 0, 0, 0 };
502 /* This clz is based on the log2(n) implementation at
503 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
504 * It is not any faster than the one above, but trades 16B in the lookup table
505 * for a savings of 12B per each inlined call.
507 static inline int clz(uint32_t v)
509 int r = 28;
510 if (v >= 0x10000)
512 v >>= 16;
513 r -= 16;
515 if (v & 0xff00)
517 v >>= 8;
518 r -= 8;
520 if (v & 0xf0)
522 v >>= 4;
523 r -= 4;
525 return r + clz_lut[v];
527 #endif
529 /* Return the maximum possible left shift for a signed int32, without
530 * overflow
532 static inline int allowed_shift(int32_t val)
534 uint32_t uval = val ^ (val >> 31);
535 return clz(uval) - 1;
538 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
539 * num and den before dividing.
541 static inline PFreal fdiv(PFreal num, PFreal den)
543 int shift = allowed_shift(num);
544 shift = MIN(PFREAL_SHIFT, shift);
545 num <<= shift;
546 den >>= PFREAL_SHIFT - shift;
547 return num / den;
550 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
551 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
552 #define fabs(a) (a < 0 ? -a : a)
553 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
555 #if CONFIG_CPU == SH7034
556 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
557 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
558 #else
559 #define MULUQ(a, b) ((a) * (b))
560 #endif
563 #if 0
564 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
565 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
567 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
568 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
570 static inline PFreal fmul(PFreal a, PFreal b)
572 return (a*b) >> PFREAL_SHIFT;
575 static inline PFreal fdiv(PFreal n, PFreal m)
577 return (n<<(PFREAL_SHIFT))/m;
579 #endif
581 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
582 static const short sin_tab[] = {
583 0, 100, 200, 297, 392, 483, 569, 650,
584 724, 792, 851, 903, 946, 980, 1004, 1019,
585 1024, 1019, 1004, 980, 946, 903, 851, 792,
586 724, 650, 569, 483, 392, 297, 200, 100,
587 0, -100, -200, -297, -392, -483, -569, -650,
588 -724, -792, -851, -903, -946, -980, -1004, -1019,
589 -1024, -1019, -1004, -980, -946, -903, -851, -792,
590 -724, -650, -569, -483, -392, -297, -200, -100,
594 static inline PFreal fsin(int iangle)
596 iangle &= IANGLE_MASK;
598 int i = (iangle >> 4);
599 PFreal p = sin_tab[i];
600 PFreal q = sin_tab[(i+1)];
601 PFreal g = (q - p);
602 return p + g * (iangle-i*16)/16;
605 static inline PFreal fcos(int iangle)
607 return fsin(iangle + (IANGLE_MAX >> 2));
610 static inline unsigned scale_val(unsigned val, unsigned bits)
612 val = val * ((1 << bits) - 1);
613 return ((val >> 8) + val + 128) >> 8;
616 static void output_row_8_transposed(uint32_t row, void * row_in,
617 struct scaler_context *ctx)
619 pix_t *dest = (pix_t*)ctx->bm->data + row;
620 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
621 #ifdef USEGSLIB
622 uint8_t *qp = (uint8_t*)row_in;
623 for (; dest < end; dest += ctx->bm->height)
624 *dest = *qp++;
625 #else
626 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
627 unsigned r, g, b;
628 for (; dest < end; dest += ctx->bm->height)
630 r = scale_val(qp->red, 5);
631 g = scale_val(qp->green, 6);
632 b = scale_val((qp++)->blue, 5);
633 *dest = LCD_RGBPACK_LCD(r,g,b);
635 #endif
638 static void output_row_32_transposed(uint32_t row, void * row_in,
639 struct scaler_context *ctx)
641 pix_t *dest = (pix_t*)ctx->bm->data + row;
642 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
643 #ifdef USEGSLIB
644 uint32_t *qp = (uint32_t*)row_in;
645 for (; dest < end; dest += ctx->bm->height)
646 *dest = SC_OUT(*qp++, ctx);
647 #else
648 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
649 int r, g, b;
650 for (; dest < end; dest += ctx->bm->height)
652 r = scale_val(SC_OUT(qp->r, ctx), 5);
653 g = scale_val(SC_OUT(qp->g, ctx), 6);
654 b = scale_val(SC_OUT(qp->b, ctx), 5);
655 qp++;
656 *dest = LCD_RGBPACK_LCD(r,g,b);
658 #endif
661 #ifdef HAVE_LCD_COLOR
662 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
663 struct scaler_context *ctx)
665 pix_t *dest = (pix_t*)ctx->bm->data + row;
666 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
667 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
668 for (; dest < end; dest += ctx->bm->height)
670 unsigned r, g, b, y, u, v;
671 y = SC_OUT(qp->b, ctx);
672 u = SC_OUT(qp->g, ctx);
673 v = SC_OUT(qp->r, ctx);
674 qp++;
675 yuv_to_rgb(y, u, v, &r, &g, &b);
676 r = scale_val(r, 5);
677 g = scale_val(g, 6);
678 b = scale_val(b, 5);
679 *dest = LCD_RGBPACK_LCD(r, g, b);
682 #endif
684 static unsigned int get_size(struct bitmap *bm)
686 return bm->width * bm->height * sizeof(pix_t);
689 const struct custom_format format_transposed = {
690 .output_row_8 = output_row_8_transposed,
691 #ifdef HAVE_LCD_COLOR
692 .output_row_32 = {
693 output_row_32_transposed,
694 output_row_32_transposed_fromyuv
696 #else
697 .output_row_32 = output_row_32_transposed,
698 #endif
699 .get_size = get_size
702 static const struct button_mapping* get_context_map(int context)
704 return pf_contexts[context & ~CONTEXT_PLUGIN];
707 /* Create the lookup table with the scaling values for the reflections */
708 void init_reflect_table(void)
710 int i;
711 for (i = 0; i < REFLECT_HEIGHT; i++)
712 reflect_table[i] =
713 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
714 (5 * REFLECT_HEIGHT);
718 Create an index of all albums from the database.
719 Also store the album names so we can access them later.
721 int create_album_index(void)
723 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
724 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
725 album_count = 0;
726 rb->tagcache_search(&tcs, tag_album);
727 unsigned int l, old_l = 0;
728 album_names = buf;
729 album[0].name_idx = 0;
730 while (rb->tagcache_get_next(&tcs))
732 buf_size -= sizeof(struct album_data);
733 l = tcs.result_len;
734 if ( album_count > 0 )
735 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
737 if ( l > buf_size )
738 /* not enough memory */
739 return ERROR_BUFFER_FULL;
741 rb->strcpy(buf, tcs.result);
742 buf_size -= l;
743 buf = l + (char *)buf;
744 album[-album_count].seek = tcs.result_seek;
745 old_l = l;
746 album_count++;
748 rb->tagcache_search_finish(&tcs);
749 ALIGN_BUFFER(buf, buf_size, 4);
750 int i;
751 struct album_data* tmp_album = (struct album_data*)buf;
752 for (i = album_count - 1; i >= 0; i--)
753 tmp_album[i] = album[-i];
754 album = tmp_album;
755 buf = album + album_count;
756 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
760 Return a pointer to the album name of the given slide_index
762 char* get_album_name(const int slide_index)
764 return album_names + album[slide_index].name_idx;
768 Return a pointer to the track name of the active album
769 create_track_index has to be called first.
771 char* get_track_name(const int track_index)
773 if ( track_index < track_count )
774 return track_names + tracks[track_index].name_idx;
775 return 0;
777 #if PF_PLAYBACK_CAPABLE
778 char* get_track_filename(const int track_index)
780 if ( track_index < track_count )
781 return track_names + tracks[track_index].filename_idx;
782 return 0;
784 #endif
786 Compare two unsigned ints passed via pointers.
788 int compare_tracks (const void *a_v, const void *b_v)
790 uint32_t a = ((struct track_data *)a_v)->sort;
791 uint32_t b = ((struct track_data *)b_v)->sort;
792 return (int)(a - b);
796 Create the track index of the given slide_index.
798 void create_track_index(const int slide_index)
800 if ( slide_index == track_index )
801 return;
802 track_index = slide_index;
804 if (!rb->tagcache_search(&tcs, tag_title))
805 goto fail;
807 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
808 track_count=0;
809 int string_index = 0, track_num;
810 int disc_num;
811 size_t out = 0;
812 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
813 borrowed += out;
814 int avail = borrowed;
815 tracks = (struct track_data*)(track_names + borrowed);
816 while (rb->tagcache_get_next(&tcs))
818 int len = 0, fn_idx = 0;
820 avail -= sizeof(struct track_data);
821 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
822 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
824 if (disc_num < 0)
825 disc_num = 0;
826 retry:
827 if (track_num >= 0)
829 if (disc_num)
830 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
831 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
832 else
833 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
834 "%d: %s", track_num + 1, tcs.result);
836 else
838 track_num = 0;
839 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
840 "%s", tcs.result);
842 if (fn_idx <= 0)
843 goto fail;
844 #if PF_PLAYBACK_CAPABLE
845 int remain = avail - fn_idx;
846 if (remain >= MAX_PATH)
847 { /* retrieve filename for building the playlist */
848 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
849 track_names + string_index + fn_idx, remain);
850 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
851 /* make sure track name and file name are really split by a \0, else
852 * get_track_name might fail */
853 *(track_names + string_index + fn_idx -1) = '\0';
856 else /* request more buffer so that track and filename fit */
857 len = (avail - remain) + MAX_PATH;
858 #else
859 len = fn_idx;
860 #endif
861 if (len > avail)
863 while (len > avail)
865 if (!free_slide_prio(0))
866 goto fail;
867 out = 0;
868 buflib_buffer_out(&buf_ctx, &out);
869 avail += out;
870 borrowed += out;
871 if (track_count)
873 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
874 unsigned int bytes = track_count * sizeof(struct track_data);
875 rb->memmove(new_tracks, tracks, bytes);
876 tracks = new_tracks;
879 goto retry;
882 avail -= len;
883 tracks--;
884 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
885 tracks->name_idx = string_index;
886 tracks->seek = tcs.result_seek;
887 #if PF_PLAYBACK_CAPABLE
888 tracks->filename_idx = fn_idx + string_index;
889 #endif
890 track_count++;
891 string_index += len;
894 rb->tagcache_search_finish(&tcs);
896 /* now fix the track list order */
897 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
898 return;
899 fail:
900 track_count = 0;
901 return;
905 Determine filename of the album art for the given slide_index and
906 store the result in buf.
907 The algorithm looks for the first track of the given album uses
908 find_albumart to find the filename.
910 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
911 int buflen)
913 if ( slide_index == -1 )
915 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
918 if (!rb->tagcache_search(&tcs, tag_filename))
919 return false;
921 bool result;
922 /* find the first track of the album */
923 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
925 if ( rb->tagcache_get_next(&tcs) ) {
926 struct mp3entry id3;
927 int fd;
929 #ifdef HAVE_TC_RAMCACHE
930 if (rb->tagcache_fill_tags(&id3, tcs.result))
932 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
934 else
935 #endif
937 fd = rb->open(tcs.result, O_RDONLY);
938 rb->get_metadata(&id3, fd, tcs.result);
939 rb->close(fd);
941 if ( search_albumart_files(&id3, ":", buf, buflen) )
942 result = true;
943 else
944 result = false;
946 else {
947 /* did not find a matching track */
948 result = false;
950 rb->tagcache_search_finish(&tcs);
951 return result;
955 Draw the PictureFlow logo
957 void draw_splashscreen(void)
959 unsigned char * buf_tmp = buf;
960 size_t buf_tmp_size = buf_size;
961 struct screen* display = rb->screens[0];
962 #if FB_DATA_SZ > 1
963 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
964 #endif
965 struct bitmap logo = {
966 #if LCD_WIDTH < 200
967 .width = 100,
968 .height = 18,
969 #else
970 .width = 193,
971 .height = 34,
972 #endif
973 .data = buf_tmp
975 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
976 NULL);
977 #if LCD_DEPTH > 1
978 rb->lcd_set_background(N_BRIGHT(0));
979 rb->lcd_set_foreground(N_BRIGHT(255));
980 #else
981 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
982 #endif
983 rb->lcd_clear_display();
985 if (ret > 0)
987 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
988 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
989 #endif
990 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
991 logo.width, logo.height);
992 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
993 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
994 #endif
997 rb->lcd_update();
1002 Draw a simple progress bar
1004 void draw_progressbar(int step)
1006 int txt_w, txt_h;
1007 const int bar_height = 22;
1008 const int w = LCD_WIDTH - 20;
1009 const int x = 10;
1011 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1013 int y = (LCD_HEIGHT - txt_h)/2;
1015 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1016 y += (txt_h + 5);
1018 #if LCD_DEPTH > 1
1019 rb->lcd_set_foreground(N_BRIGHT(100));
1020 #endif
1021 rb->lcd_drawrect(x, y, w+2, bar_height);
1022 #if LCD_DEPTH > 1
1023 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1024 #endif
1026 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1027 #if LCD_DEPTH > 1
1028 rb->lcd_set_foreground(N_BRIGHT(255));
1029 #endif
1030 rb->lcd_update();
1031 rb->yield();
1035 Precomupte the album art images and store them in CACHE_PREFIX.
1037 bool create_albumart_cache(void)
1039 int ret;
1041 int i, slides = 0;
1042 struct bitmap input_bmp;
1044 char pfraw_file[MAX_PATH];
1045 char albumart_file[MAX_PATH];
1046 unsigned int format = FORMAT_NATIVE;
1047 cache_version = 0;
1048 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1049 if (resize)
1050 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1051 for (i=0; i < album_count; i++)
1053 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1055 /* delete existing cache, so it's a true rebuild */
1056 if(rb->file_exists(pfraw_file))
1057 rb->remove(pfraw_file);
1058 draw_progressbar(i);
1059 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1060 continue;
1062 input_bmp.data = buf;
1063 input_bmp.width = DISPLAY_WIDTH;
1064 input_bmp.height = DISPLAY_HEIGHT;
1065 ret = read_image_file(albumart_file, &input_bmp,
1066 buf_size, format, &format_transposed);
1067 if (ret <= 0) {
1068 rb->splash(HZ, "Could not read bmp");
1069 continue; /* skip missing/broken files */
1071 if (!save_pfraw(pfraw_file, &input_bmp))
1073 rb->splash(HZ, "Could not write bmp");
1075 slides++;
1076 if ( rb->button_get(false) == PF_MENU ) return false;
1078 if ( slides == 0 ) {
1079 /* Warn the user that we couldn't find any albumart */
1080 rb->splash(2*HZ, "No album art found");
1081 return false;
1083 return true;
1087 Thread used for loading and preparing bitmaps in the background
1089 void thread(void)
1091 long sleep_time = 5 * HZ;
1092 struct queue_event ev;
1093 while (1) {
1094 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1095 switch (ev.id) {
1096 case EV_EXIT:
1097 return;
1098 case EV_WAKEUP:
1099 /* we just woke up */
1100 break;
1102 while ( load_new_slide() ) {
1103 rb->yield();
1104 switch (ev.id) {
1105 case EV_EXIT:
1106 return;
1114 End the thread by posting the EV_EXIT event
1116 void end_pf_thread(void)
1118 if ( thread_is_running ) {
1119 rb->queue_post(&thread_q, EV_EXIT, 0);
1120 rb->thread_wait(thread_id);
1121 /* remove the thread's queue from the broadcast list */
1122 rb->queue_delete(&thread_q);
1123 thread_is_running = false;
1130 Create the thread an setup the event queue
1132 bool create_pf_thread(void)
1134 /* put the thread's queue in the bcast list */
1135 rb->queue_init(&thread_q, true);
1136 if ((thread_id = rb->create_thread(
1137 thread,
1138 thread_stack,
1139 sizeof(thread_stack),
1141 "Picture load thread"
1142 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1143 PRIORITY_REALTIME + 1))
1144 IF_COP(, CPU)
1146 ) == 0) {
1147 return false;
1149 thread_is_running = true;
1150 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1151 return true;
1155 Safe the given bitmap as filename in the pfraw format
1157 bool save_pfraw(char* filename, struct bitmap *bm)
1159 struct pfraw_header bmph;
1160 bmph.width = bm->width;
1161 bmph.height = bm->height;
1162 int fh = rb->creat( filename );
1163 if( fh < 0 ) return false;
1164 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1165 int y;
1166 for( y = 0; y < bm->height; y++ )
1168 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1169 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1171 rb->close( fh );
1172 return true;
1177 * The following functions implement the linked-list-in-array used to manage
1178 * the LRU cache of slides, and the list of free cache slots.
1181 #define seek_right_while(start, cond) \
1182 ({ \
1183 int ind_, next_ = (start); \
1184 do { \
1185 ind_ = next_; \
1186 next_ = cache[ind_].next; \
1187 } while (next_ != cache_used && (cond)); \
1188 ind_; \
1191 #define seek_left_while(start, cond) \
1192 ({ \
1193 int ind_, next_ = (start); \
1194 do { \
1195 ind_ = next_; \
1196 next_ = cache[ind_].prev; \
1197 } while (ind_ != cache_used && (cond)); \
1198 ind_; \
1202 Pop the given item from the linked list starting at *head, returning the next
1203 item, or -1 if the list is now empty.
1205 static inline int lla_pop_item (int *head, int i)
1207 int prev = cache[i].prev;
1208 int next = cache[i].next;
1209 if (i == next)
1211 *head = -1;
1212 return -1;
1214 else if (i == *head)
1215 *head = next;
1216 cache[next].prev = prev;
1217 cache[prev].next = next;
1218 return next;
1223 Pop the head item from the list starting at *head, returning the index of the
1224 item, or -1 if the list is already empty.
1226 static inline int lla_pop_head (int *head)
1228 int i = *head;
1229 if (i != -1)
1230 lla_pop_item(head, i);
1231 return i;
1235 Insert the item at index i before the one at index p.
1237 static inline void lla_insert (int i, int p)
1239 int next = p;
1240 int prev = cache[next].prev;
1241 cache[next].prev = i;
1242 cache[prev].next = i;
1243 cache[i].next = next;
1244 cache[i].prev = prev;
1249 Insert the item at index i at the end of the list starting at *head.
1251 static inline void lla_insert_tail (int *head, int i)
1253 if (*head == -1)
1255 *head = i;
1256 cache[i].next = i;
1257 cache[i].prev = i;
1258 } else
1259 lla_insert(i, *head);
1263 Insert the item at index i before the one at index p.
1265 static inline void lla_insert_after(int i, int p)
1267 p = cache[p].next;
1268 lla_insert(i, p);
1273 Insert the item at index i before the one at index p in the list starting at
1274 *head
1276 static inline void lla_insert_before(int *head, int i, int p)
1278 lla_insert(i, p);
1279 if (*head == p)
1280 *head = i;
1285 Free the used slide at index i, and its buffer, and move it to the free
1286 slides list.
1288 static inline void free_slide(int i)
1290 if (cache[i].hid != empty_slide_hid)
1291 buflib_free(&buf_ctx, cache[i].hid);
1292 cache[i].index = -1;
1293 lla_pop_item(&cache_used, i);
1294 lla_insert_tail(&cache_free, i);
1295 if (cache_used == -1)
1297 cache_right_index = -1;
1298 cache_left_index = -1;
1299 cache_center_index = -1;
1305 Free one slide ranked above the given priority. If no such slide can be found,
1306 return false.
1308 static bool free_slide_prio(int prio)
1310 if (cache_used == -1)
1311 return false;
1312 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1313 int prio_l = cache[l].index < center_index ?
1314 center_index - cache[l].index : 0;
1315 int prio_r = cache[r].index > center_index ?
1316 cache[r].index - center_index : 0;
1317 if (prio_l > prio_r)
1319 i = l;
1320 prio_max = prio_l;
1321 } else {
1322 i = r;
1323 prio_max = prio_r;
1325 if (prio_max > prio)
1327 if (i == cache_left_index)
1328 cache_left_index = cache[i].next;
1329 if (i == cache_right_index)
1330 cache_right_index = cache[i].prev;
1331 free_slide(i);
1332 return true;
1333 } else
1334 return false;
1338 Read the pfraw image given as filename and return the hid of the buffer
1340 int read_pfraw(char* filename, int prio)
1342 struct pfraw_header bmph;
1343 int fh = rb->open(filename, O_RDONLY);
1344 if( fh < 0 )
1345 return empty_slide_hid;
1346 else
1347 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1349 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1350 bmph.width * bmph.height;
1352 int hid;
1353 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1355 if (!hid) {
1356 rb->close( fh );
1357 return 0;
1360 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1362 bm->width = bmph.width;
1363 bm->height = bmph.height;
1364 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1366 int y;
1367 for( y = 0; y < bm->height; y++ )
1369 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1370 data += bm->width;
1372 rb->close( fh );
1373 return hid;
1378 Load the surface for the given slide_index into the cache at cache_index.
1380 static inline bool load_and_prepare_surface(const int slide_index,
1381 const int cache_index,
1382 const int prio)
1384 char tmp_path_name[MAX_PATH+1];
1385 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1386 slide_index);
1388 int hid = read_pfraw(tmp_path_name, prio);
1389 if (!hid)
1390 return false;
1392 cache[cache_index].hid = hid;
1394 if ( cache_index < SLIDE_CACHE_SIZE ) {
1395 cache[cache_index].index = slide_index;
1398 return true;
1403 Load the "next" slide that we can load, freeing old slides if needed, provided
1404 that they are further from center_index than the current slide
1406 bool load_new_slide(void)
1408 int i = -1;
1409 if (cache_center_index != -1)
1411 int next, prev;
1412 if (cache[cache_center_index].index != center_index)
1414 if (cache[cache_center_index].index < center_index)
1416 cache_center_index = seek_right_while(cache_center_index,
1417 cache[next_].index <= center_index);
1418 prev = cache_center_index;
1419 next = cache[cache_center_index].next;
1421 else
1423 cache_center_index = seek_left_while(cache_center_index,
1424 cache[next_].index >= center_index);
1425 next = cache_center_index;
1426 prev = cache[cache_center_index].prev;
1428 if (cache[cache_center_index].index != center_index)
1430 if (cache_free == -1)
1431 free_slide_prio(0);
1432 i = lla_pop_head(&cache_free);
1433 if (!load_and_prepare_surface(center_index, i, 0))
1434 goto fail_and_refree;
1435 if (cache[next].index == -1)
1437 if (cache[prev].index == -1)
1438 goto insert_first_slide;
1439 else
1440 next = cache[prev].next;
1442 lla_insert(i, next);
1443 if (cache[i].index < cache[cache_used].index)
1444 cache_used = i;
1445 cache_center_index = i;
1446 cache_left_index = i;
1447 cache_right_index = i;
1448 return true;
1451 if (cache[cache_left_index].index >
1452 cache[cache_center_index].index)
1453 cache_left_index = cache_center_index;
1454 if (cache[cache_right_index].index <
1455 cache[cache_center_index].index)
1456 cache_right_index = cache_center_index;
1457 cache_left_index = seek_left_while(cache_left_index,
1458 cache[ind_].index - 1 == cache[next_].index);
1459 cache_right_index = seek_right_while(cache_right_index,
1460 cache[ind_].index - 1 == cache[next_].index);
1461 int prio_l = cache[cache_center_index].index -
1462 cache[cache_left_index].index + 1;
1463 int prio_r = cache[cache_right_index].index -
1464 cache[cache_center_index].index + 1;
1465 if ((prio_l < prio_r ||
1466 cache[cache_right_index].index >= number_of_slides) &&
1467 cache[cache_left_index].index > 0)
1469 if (cache_free == -1 && !free_slide_prio(prio_l))
1470 return false;
1471 i = lla_pop_head(&cache_free);
1472 if (load_and_prepare_surface(cache[cache_left_index].index
1473 - 1, i, prio_l))
1475 lla_insert_before(&cache_used, i, cache_left_index);
1476 cache_left_index = i;
1477 return true;
1479 } else if(cache[cache_right_index].index < number_of_slides - 1)
1481 if (cache_free == -1 && !free_slide_prio(prio_r))
1482 return false;
1483 i = lla_pop_head(&cache_free);
1484 if (load_and_prepare_surface(cache[cache_right_index].index
1485 + 1, i, prio_r))
1487 lla_insert_after(i, cache_right_index);
1488 cache_right_index = i;
1489 return true;
1492 } else {
1493 i = lla_pop_head(&cache_free);
1494 if (load_and_prepare_surface(center_index, i, 0))
1496 insert_first_slide:
1497 cache[i].next = i;
1498 cache[i].prev = i;
1499 cache_center_index = i;
1500 cache_left_index = i;
1501 cache_right_index = i;
1502 cache_used = i;
1503 return true;
1506 fail_and_refree:
1507 if (i != -1)
1509 lla_insert_tail(&cache_free, i);
1511 return false;
1516 Get a slide from the buffer
1518 static inline struct dim *get_slide(const int hid)
1520 if (!hid)
1521 return NULL;
1523 struct dim *bmp;
1525 bmp = buflib_get_data(&buf_ctx, hid);
1527 return bmp;
1532 Return the requested surface
1534 static inline struct dim *surface(const int slide_index)
1536 if (slide_index < 0)
1537 return 0;
1538 if (slide_index >= number_of_slides)
1539 return 0;
1540 int i;
1541 if ((i = cache_used ) != -1)
1543 do {
1544 if (cache[i].index == slide_index)
1545 return get_slide(cache[i].hid);
1546 i = cache[i].next;
1547 } while (i != cache_used);
1549 return get_slide(empty_slide_hid);
1553 adjust slides so that they are in "steady state" position
1555 void reset_slides(void)
1557 center_slide.angle = 0;
1558 center_slide.cx = 0;
1559 center_slide.cy = 0;
1560 center_slide.distance = 0;
1561 center_slide.slide_index = center_index;
1563 int i;
1564 for (i = 0; i < num_slides; i++) {
1565 struct slide_data *si = &left_slides[i];
1566 si->angle = itilt;
1567 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1568 si->cy = offsetY;
1569 si->slide_index = center_index - 1 - i;
1570 si->distance = 0;
1573 for (i = 0; i < num_slides; i++) {
1574 struct slide_data *si = &right_slides[i];
1575 si->angle = -itilt;
1576 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1577 si->cy = offsetY;
1578 si->slide_index = center_index + 1 + i;
1579 si->distance = 0;
1585 Updates look-up table and other stuff necessary for the rendering.
1586 Call this when the viewport size or slide dimension is changed.
1588 * To calculate the offset that will provide the proper margin, we use the same
1589 * projection used to render the slides. The solution for xc, the slide center,
1590 * is:
1591 * xp * (zo + xs * sin(r))
1592 * xc = xp - xs * cos(r) + ───────────────────────
1594 * TODO: support moving the side slides toward or away from the camera
1596 void recalc_offsets(void)
1598 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1599 PFreal zo;
1600 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1601 PFREAL_ONE) * zoom / 100;
1602 PFreal cosr, sinr;
1604 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1605 cosr = fcos(-itilt);
1606 sinr = fsin(-itilt);
1607 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1608 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1609 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1610 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1611 / CAM_DIST;
1612 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1617 Fade the given color by spreading the fb_data (ushort)
1618 to an uint, multiply and compress the result back to a ushort.
1620 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1621 static inline unsigned fade_color(pix_t c, unsigned a)
1623 unsigned int result;
1624 c = swap16(c);
1625 a = (a + 2) & 0x1fc;
1626 result = ((c & 0xf81f) * a) & 0xf81f00;
1627 result |= ((c & 0x7e0) * a) & 0x7e000;
1628 result >>= 8;
1629 return swap16(result);
1631 #elif LCD_PIXELFORMAT == RGB565
1632 static inline unsigned fade_color(pix_t c, unsigned a)
1634 unsigned int result;
1635 a = (a + 2) & 0x1fc;
1636 result = ((c & 0xf81f) * a) & 0xf81f00;
1637 result |= ((c & 0x7e0) * a) & 0x7e000;
1638 result >>= 8;
1639 return result;
1641 #else
1642 static inline unsigned fade_color(pix_t c, unsigned a)
1644 unsigned val = c;
1645 return MULUQ(val, a) >> 8;
1647 #endif
1650 * Render a single slide
1651 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1652 * on the slide from its center, zo is the slide's depth offset from the plane
1653 * of the display, r is the angle at which the slide is tilted, and xp is the
1654 * point on the display corresponding to xs on the slide, the projection
1655 * formulas are:
1657 * z * (xc + xs * cos(r))
1658 * xp = ──────────────────────
1659 * z + zo + xs * sin(r)
1661 * z * (xc - xp) - xp * zo
1662 * xs = ────────────────────────
1663 * xp * sin(r) - z * cos(r)
1665 * We use the xp projection once, to find the left edge of the slide on the
1666 * display. From there, we use the xs reverse projection to find the horizontal
1667 * offset from the slide center of each column on the screen, until we reach
1668 * the right edge of the slide, or the screen. The reverse projection can be
1669 * optimized by saving the numerator and denominator of the fraction, which can
1670 * then be incremented by (z + zo) and sin(r) respectively.
1672 void render_slide(struct slide_data *slide, const int alpha)
1674 struct dim *bmp = surface(slide->slide_index);
1675 if (!bmp) {
1676 return;
1678 if (slide->angle > 255 || slide->angle < -255)
1679 return;
1680 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1682 const int sw = bmp->width;
1683 const int sh = bmp->height;
1684 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1685 const int w = LCD_WIDTH;
1687 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1689 if (alpha == 256) { /* opaque -> copy table */
1690 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1691 } else { /* precalculate faded table */
1692 int i, lalpha;
1693 for (i = 0; i < REFLECT_HEIGHT; i++) {
1694 lalpha = reflect_table[i];
1695 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1699 PFreal cosr = fcos(slide->angle);
1700 PFreal sinr = fsin(slide->angle);
1701 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1702 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1703 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1704 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1705 (CAM_DIST_R + zo + fmul(xs,sinr)));
1707 /* Since we're finding the screen position of the left edge of the slide,
1708 * we round up.
1710 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1711 >> PFREAL_SHIFT;
1712 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1713 if (xi >= w) {
1714 return;
1716 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1717 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1718 xs = fdiv(xsnum, xsden);
1720 xsnumi = -CAM_DIST_R - zo;
1721 xsdeni = sinr;
1722 int x;
1723 int dy = PFREAL_ONE;
1724 for (x = xi; x < w; x++) {
1725 int column = (xs - slide_left) / PFREAL_ONE;
1726 if (column >= sw)
1727 break;
1728 if (zo || slide->angle)
1729 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1731 const pix_t *ptr = &src[column * bmp->height];
1733 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1734 #define PIXELSTEP_Y 1
1735 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1736 #else
1737 #define PIXELSTEP_Y BUFFER_WIDTH
1738 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1739 #endif
1741 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1742 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1743 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1745 if (alpha == 256) {
1746 while (p >= plim) {
1747 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1748 p -= dy;
1749 pixel -= PIXELSTEP_Y;
1751 } else {
1752 while (p >= plim) {
1753 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1754 p -= dy;
1755 pixel -= PIXELSTEP_Y;
1758 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1759 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1760 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1761 p + (LCD_HEIGHT/2) * dy);
1762 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1764 if (alpha == 256) {
1765 while (p < plim) {
1766 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1767 p += dy;
1768 pixel += PIXELSTEP_Y;
1770 } else {
1771 while (p < plim) {
1772 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1773 p += dy;
1774 pixel += PIXELSTEP_Y;
1777 while (p < plim2) {
1778 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1779 int lalpha = reftab[ty];
1780 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1781 p += dy;
1782 pixel += PIXELSTEP_Y;
1785 if (zo || slide->angle)
1787 xsnum += xsnumi;
1788 xsden += xsdeni;
1789 xs = fdiv(xsnum, xsden);
1790 } else
1791 xs += PFREAL_ONE;
1794 /* let the music play... */
1795 rb->yield();
1796 return;
1800 Jump the the given slide_index
1802 static inline void set_current_slide(const int slide_index)
1804 int old_center_index = center_index;
1805 step = 0;
1806 center_index = fbound(slide_index, 0, number_of_slides - 1);
1807 if (old_center_index != center_index)
1808 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1809 target = center_index;
1810 slide_frame = slide_index << 16;
1811 reset_slides();
1815 Start the animation for changing slides
1817 void start_animation(void)
1819 step = (target < center_slide.slide_index) ? -1 : 1;
1820 pf_state = pf_scrolling;
1824 Go to the previous slide
1826 void show_previous_slide(void)
1828 if (step == 0) {
1829 if (center_index > 0) {
1830 target = center_index - 1;
1831 start_animation();
1833 } else if ( step > 0 ) {
1834 target = center_index;
1835 start_animation();
1836 } else {
1837 target = fmax(0, center_index - 2);
1843 Go to the next slide
1845 void show_next_slide(void)
1847 if (step == 0) {
1848 if (center_index < number_of_slides - 1) {
1849 target = center_index + 1;
1850 start_animation();
1852 } else if ( step < 0 ) {
1853 target = center_index;
1854 start_animation();
1855 } else {
1856 target = fmin(center_index + 2, number_of_slides - 1);
1862 Render the slides. Updates only the offscreen buffer.
1864 void render_all_slides(void)
1866 MYLCD(set_background)(G_BRIGHT(0));
1867 /* TODO: Optimizes this by e.g. invalidating rects */
1868 MYLCD(clear_display)();
1870 int nleft = num_slides;
1871 int nright = num_slides;
1873 int index;
1874 if (step == 0) {
1875 /* no animation, boring plain rendering */
1876 for (index = nleft - 2; index >= 0; index--) {
1877 int alpha = (index < nleft - 2) ? 256 : 128;
1878 alpha -= extra_fade;
1879 if (alpha > 0 )
1880 render_slide(&left_slides[index], alpha);
1882 for (index = nright - 2; index >= 0; index--) {
1883 int alpha = (index < nright - 2) ? 256 : 128;
1884 alpha -= extra_fade;
1885 if (alpha > 0 )
1886 render_slide(&right_slides[index], alpha);
1888 } else {
1889 /* the first and last slide must fade in/fade out */
1890 for (index = nleft - 1; index >= 0; index--) {
1891 int alpha = 256;
1892 if (index == nleft - 1)
1893 alpha = (step > 0) ? 0 : 128 - fade / 2;
1894 if (index == nleft - 2)
1895 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1896 if (index == nleft - 3)
1897 alpha = (step > 0) ? 256 - fade / 2 : 256;
1898 render_slide(&left_slides[index], alpha);
1900 for (index = nright - 1; index >= 0; index--) {
1901 int alpha = (index < nright - 2) ? 256 : 128;
1902 if (index == nright - 1)
1903 alpha = (step > 0) ? fade / 2 : 0;
1904 if (index == nright - 2)
1905 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1906 if (index == nright - 3)
1907 alpha = (step > 0) ? 256 : 128 + fade / 2;
1908 render_slide(&right_slides[index], alpha);
1911 render_slide(&center_slide, 256);
1916 Updates the animation effect. Call this periodically from a timer.
1918 void update_scroll_animation(void)
1920 if (step == 0)
1921 return;
1923 int speed = 16384;
1924 int i;
1926 /* deaccelerate when approaching the target */
1927 if (true) {
1928 const int max = 2 * 65536;
1930 int fi = slide_frame;
1931 fi -= (target << 16);
1932 if (fi < 0)
1933 fi = -fi;
1934 fi = fmin(fi, max);
1936 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1937 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1940 slide_frame += speed * step;
1942 int index = slide_frame >> 16;
1943 int pos = slide_frame & 0xffff;
1944 int neg = 65536 - pos;
1945 int tick = (step < 0) ? neg : pos;
1946 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1948 /* the leftmost and rightmost slide must fade away */
1949 fade = pos / 256;
1951 if (step < 0)
1952 index++;
1953 if (center_index != index) {
1954 center_index = index;
1955 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1956 slide_frame = index << 16;
1957 center_slide.slide_index = center_index;
1958 for (i = 0; i < num_slides; i++)
1959 left_slides[i].slide_index = center_index - 1 - i;
1960 for (i = 0; i < num_slides; i++)
1961 right_slides[i].slide_index = center_index + 1 + i;
1964 center_slide.angle = (step * tick * itilt) >> 16;
1965 center_slide.cx = -step * fmul(offsetX, ftick);
1966 center_slide.cy = fmul(offsetY, ftick);
1968 if (center_index == target) {
1969 reset_slides();
1970 pf_state = pf_idle;
1971 step = 0;
1972 fade = 256;
1973 return;
1976 for (i = 0; i < num_slides; i++) {
1977 struct slide_data *si = &left_slides[i];
1978 si->angle = itilt;
1979 si->cx =
1980 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1981 * slide_spacing * ftick);
1982 si->cy = offsetY;
1985 for (i = 0; i < num_slides; i++) {
1986 struct slide_data *si = &right_slides[i];
1987 si->angle = -itilt;
1988 si->cx =
1989 offsetX + slide_spacing * i * PFREAL_ONE - step
1990 * slide_spacing * ftick;
1991 si->cy = offsetY;
1994 if (step > 0) {
1995 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1996 right_slides[0].angle = -(neg * itilt) >> 16;
1997 right_slides[0].cx = fmul(offsetX, ftick);
1998 right_slides[0].cy = fmul(offsetY, ftick);
1999 } else {
2000 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2001 left_slides[0].angle = (pos * itilt) >> 16;
2002 left_slides[0].cx = -fmul(offsetX, ftick);
2003 left_slides[0].cy = fmul(offsetY, ftick);
2006 /* must change direction ? */
2007 if (target < index)
2008 if (step > 0)
2009 step = -1;
2010 if (target > index)
2011 if (step < 0)
2012 step = 1;
2017 Cleanup the plugin
2019 void cleanup(void *parameter)
2021 (void) parameter;
2022 int i;
2023 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2024 rb->cpu_boost(false);
2025 #endif
2026 end_pf_thread();
2027 /* Turn on backlight timeout (revert to settings) */
2028 backlight_use_settings(); /* backlight control in lib/helper.c */
2030 #ifdef USEGSLIB
2031 grey_release();
2032 #endif
2033 FOR_NB_SCREENS(i)
2034 rb->viewportmanager_theme_undo(i, false);
2038 Create the "?" slide, that is shown while loading
2039 or when no cover was found.
2041 int create_empty_slide(bool force)
2043 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2044 struct bitmap input_bmp;
2045 int ret;
2046 input_bmp.width = DISPLAY_WIDTH;
2047 input_bmp.height = DISPLAY_HEIGHT;
2048 #if LCD_DEPTH > 1
2049 input_bmp.format = FORMAT_NATIVE;
2050 #endif
2051 input_bmp.data = (char*)buf;
2052 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2053 buf_size,
2054 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2055 &format_transposed);
2056 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2057 return false;
2060 return true;
2064 Shows the settings menu
2066 int settings_menu(void)
2068 int selection = 0;
2069 bool old_val;
2071 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2072 "Spacing", "Centre margin", "Number of slides", "Zoom",
2073 "Show album title", "Resize Covers", "Rebuild cache");
2075 static const struct opt_items album_name_options[] = {
2076 { "Hide album title", -1 },
2077 { "Show at the bottom", -1 },
2078 { "Show at the top", -1 }
2081 do {
2082 selection=rb->do_menu(&settings_menu,&selection, NULL, true);
2083 switch(selection) {
2084 case 0:
2085 rb->set_bool("Show FPS", &show_fps);
2086 reset_track_list();
2087 break;
2089 case 1:
2090 rb->set_int("Spacing between slides", "", 1,
2091 &slide_spacing,
2092 NULL, 1, 0, 100, NULL );
2093 recalc_offsets();
2094 reset_slides();
2095 break;
2097 case 2:
2098 rb->set_int("Centre margin", "", 1,
2099 &center_margin,
2100 NULL, 1, 0, 80, NULL );
2101 recalc_offsets();
2102 reset_slides();
2103 break;
2105 case 3:
2106 rb->set_int("Number of slides", "", 1, &num_slides,
2107 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2108 recalc_offsets();
2109 reset_slides();
2110 break;
2112 case 4:
2113 rb->set_int("Zoom", "", 1, &zoom,
2114 NULL, 1, 10, 300, NULL );
2115 recalc_offsets();
2116 reset_slides();
2117 break;
2118 case 5:
2119 rb->set_option("Show album title", &show_album_name,
2120 INT, album_name_options, 3, NULL);
2121 reset_track_list();
2122 recalc_offsets();
2123 reset_slides();
2124 break;
2125 case 6:
2126 old_val = resize;
2127 rb->set_bool("Resize Covers", &resize);
2128 if (old_val == resize) /* changed? */
2129 break;
2130 /* fallthrough if changed, since cache needs to be rebuilt */
2131 case 7:
2132 cache_version = 0;
2133 rb->remove(EMPTY_SLIDE);
2134 rb->splash(HZ, "Cache will be rebuilt on next restart");
2135 break;
2137 case MENU_ATTACHED_USB:
2138 return PLUGIN_USB_CONNECTED;
2140 } while ( selection >= 0 );
2141 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2142 return 0;
2146 Show the main menu
2148 enum {
2149 PF_GOTO_WPS,
2150 #if PF_PLAYBACK_CAPABLE
2151 PF_MENU_PLAYBACK_CONTROL,
2152 #endif
2153 PF_MENU_SETTINGS,
2154 PF_MENU_RETURN,
2155 PF_MENU_QUIT,
2158 int main_menu(void)
2160 int selection = 0;
2161 int result;
2163 #if LCD_DEPTH > 1
2164 rb->lcd_set_foreground(N_BRIGHT(255));
2165 #endif
2167 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2168 "Go to WPS",
2169 #if PF_PLAYBACK_CAPABLE
2170 "Playback Control",
2171 #endif
2172 "Settings", "Return", "Quit");
2173 while (1) {
2174 switch (rb->do_menu(&main_menu,&selection, NULL, true)) {
2175 case PF_GOTO_WPS: /* WPS */
2176 return -2;
2177 #if PF_PLAYBACK_CAPABLE
2178 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2179 playback_control(NULL);
2180 break;
2181 #endif
2182 case PF_MENU_SETTINGS:
2183 result = settings_menu();
2184 if ( result != 0 ) return result;
2185 break;
2186 case PF_MENU_RETURN:
2187 return 0;
2188 case PF_MENU_QUIT:
2189 return -1;
2191 case MENU_ATTACHED_USB:
2192 return PLUGIN_USB_CONNECTED;
2194 default:
2195 return 0;
2201 Animation step for zooming into the current cover
2203 void update_cover_in_animation(void)
2205 cover_animation_keyframe++;
2206 if( cover_animation_keyframe < 20 ) {
2207 center_slide.distance-=5;
2208 center_slide.angle+=1;
2209 extra_fade += 13;
2211 else if( cover_animation_keyframe < 35 ) {
2212 center_slide.angle+=16;
2214 else {
2215 cover_animation_keyframe = 0;
2216 pf_state = pf_show_tracks;
2221 Animation step for zooming out the current cover
2223 void update_cover_out_animation(void)
2225 cover_animation_keyframe++;
2226 if( cover_animation_keyframe <= 15 ) {
2227 center_slide.angle-=16;
2229 else if( cover_animation_keyframe < 35 ) {
2230 center_slide.distance+=5;
2231 center_slide.angle-=1;
2232 extra_fade -= 13;
2234 else {
2235 cover_animation_keyframe = 0;
2236 pf_state = pf_idle;
2241 Draw a blue gradient at y with height h
2243 static inline void draw_gradient(int y, int h)
2245 static int r, inc, c;
2246 inc = (100 << 8) / h;
2247 c = 0;
2248 selected_track_pulse = (selected_track_pulse+1) % 10;
2249 int c2 = selected_track_pulse - 5;
2250 for (r=0; r<h; r++) {
2251 #ifdef HAVE_LCD_COLOR
2252 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2253 c2+250-(c >> 8)));
2254 #else
2255 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2256 #endif
2257 MYLCD(hline)(0, LCD_WIDTH, r+y);
2258 if ( r > h/2 )
2259 c-=inc;
2260 else
2261 c+=inc;
2266 static void track_list_yh(int char_height)
2268 switch (show_album_name)
2270 case album_name_hide:
2271 track_list_y = (show_fps ? char_height : 0);
2272 track_list_h = LCD_HEIGHT - track_list_y;
2273 break;
2274 case album_name_bottom:
2275 track_list_y = (show_fps ? char_height : 0);
2276 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2277 break;
2278 default: /* case album_name_top */
2279 track_list_y = char_height * 2;
2280 track_list_h = LCD_HEIGHT - track_list_y -
2281 (show_fps ? char_height : 0);
2282 break;
2287 Reset the track list after a album change
2289 void reset_track_list(void)
2291 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2292 track_list_yh(albumtxt_h);
2293 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2294 start_index_track_list = 0;
2295 track_scroll_index = 0;
2296 track_scroll_dir = 1;
2297 selected_track = 0;
2299 /* let the tracklist start more centered
2300 * if the screen isn't filled with tracks */
2301 if (track_count*albumtxt_h < track_list_h)
2303 track_list_h = track_count * albumtxt_h;
2304 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2309 Display the list of tracks
2311 void show_track_list(void)
2313 MYLCD(clear_display)();
2314 if ( center_slide.slide_index != track_index ) {
2315 create_track_index(center_slide.slide_index);
2316 reset_track_list();
2318 static int titletxt_w, titletxt_x, color, titletxt_h;
2319 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2321 int titletxt_y = track_list_y;
2322 int track_i;
2323 track_i = start_index_track_list;
2324 for (;track_i < track_list_visible_entries+start_index_track_list;
2325 track_i++)
2327 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2328 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2329 if ( track_i == selected_track ) {
2330 draw_gradient(titletxt_y, titletxt_h);
2331 MYLCD(set_foreground)(G_BRIGHT(255));
2332 if (titletxt_w > LCD_WIDTH ) {
2333 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2334 track_scroll_dir = 1;
2335 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2336 track_scroll_index += track_scroll_dir*2;
2337 titletxt_x = track_scroll_index;
2339 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2341 else {
2342 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2343 MYLCD(set_foreground)(G_BRIGHT(color));
2344 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2346 titletxt_y += titletxt_h;
2350 void select_next_track(void)
2352 if ( selected_track < track_count - 1 ) {
2353 selected_track++;
2354 track_scroll_index = 0;
2355 track_scroll_dir = 1;
2356 if (selected_track==(track_list_visible_entries+start_index_track_list))
2357 start_index_track_list++;
2361 void select_prev_track(void)
2363 if (selected_track > 0 ) {
2364 if (selected_track==start_index_track_list) start_index_track_list--;
2365 track_scroll_index = 0;
2366 track_scroll_dir = 1;
2367 selected_track--;
2371 #if PF_PLAYBACK_CAPABLE
2373 * Puts the current tracklist into a newly created playlist and starts playling
2375 void start_playback(void)
2377 static int old_playlist = -1, old_shuffle = 0;
2378 int count = 0;
2379 int position = selected_track;
2380 int shuffle = rb->global_settings->playlist_shuffle;
2381 /* reuse existing playlist if possible
2382 * regenerate if shuffle is on or changed, since playlist index and
2383 * selected track are "out of sync" */
2384 if (!shuffle && center_slide.slide_index == old_playlist
2385 && (old_shuffle == shuffle))
2387 goto play;
2389 /* First, replace the current playlist with a new one */
2390 else if (rb->playlist_remove_all_tracks(NULL) == 0
2391 && rb->playlist_create(NULL, NULL) == 0)
2393 do {
2394 rb->yield();
2395 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2396 PLAYLIST_INSERT_LAST, false, true) < 0)
2397 break;
2398 } while(++count < track_count);
2399 rb->playlist_sync(NULL);
2401 else
2402 return;
2404 if (rb->global_settings->playlist_shuffle)
2405 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2406 play:
2407 /* TODO: can we adjust selected_track if !play_selected ?
2408 * if shuffle, we can't predict the playing track easily, and for either
2409 * case the track list doesn't get auto scrolled*/
2410 rb->playlist_start(position, 0);
2411 old_playlist = center_slide.slide_index;
2412 old_shuffle = shuffle;
2414 #endif
2417 Draw the current album name
2419 void draw_album_text(void)
2421 if (0 == show_album_name)
2422 return;
2424 int albumtxt_w, albumtxt_h;
2425 int albumtxt_y = 0;
2427 char *albumtxt;
2428 int c;
2429 /* Draw album text */
2430 if ( pf_state == pf_scrolling ) {
2431 c = ((slide_frame & 0xffff )/ 255);
2432 if (step < 0) c = 255-c;
2433 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2434 albumtxt = get_album_name(center_index+step);
2435 c = (c-128)*2;
2437 else {
2438 albumtxt = get_album_name(center_index);
2439 c = (128-c)*2;
2442 else {
2443 c= 255;
2444 albumtxt = get_album_name(center_index);
2447 MYLCD(set_foreground)(G_BRIGHT(c));
2448 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2449 if (center_index != prev_center_index) {
2450 albumtxt_x = 0;
2451 albumtxt_dir = -1;
2452 prev_center_index = center_index;
2455 if (show_album_name == album_name_top)
2456 albumtxt_y = albumtxt_h / 2;
2457 else
2458 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2460 if (albumtxt_w > LCD_WIDTH ) {
2461 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2462 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2463 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2464 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2465 albumtxt_x += albumtxt_dir;
2468 else {
2469 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2476 Display an error message and wait for input.
2478 void error_wait(const char *message)
2480 rb->splashf(0, "%s. Press any button to continue.", message);
2481 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2482 rb->yield();
2483 rb->sleep(2 * HZ);
2487 Main function that also contain the main plasma
2488 algorithm.
2490 int main(void)
2492 int ret;
2494 rb->lcd_setfont(FONT_UI);
2495 draw_splashscreen();
2497 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2498 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2499 error_wait("Could not create directory " CACHE_PREFIX);
2500 return PLUGIN_ERROR;
2504 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2506 init_reflect_table();
2508 ALIGN_BUFFER(buf, buf_size, 4);
2509 ret = create_album_index();
2510 if (ret == ERROR_BUFFER_FULL) {
2511 error_wait("Not enough memory for album names");
2512 return PLUGIN_ERROR;
2513 } else if (ret == ERROR_NO_ALBUMS) {
2514 error_wait("No albums found. Please enable database");
2515 return PLUGIN_ERROR;
2518 ALIGN_BUFFER(buf, buf_size, 4);
2519 number_of_slides = album_count;
2520 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2521 error_wait("Could not create album art cache");
2522 return PLUGIN_ERROR;
2525 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2526 error_wait("Could not load the empty slide");
2527 return PLUGIN_ERROR;
2529 cache_version = CACHE_VERSION;
2530 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2533 #ifdef USEGSLIB
2534 long grey_buf_used;
2535 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2536 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2538 error_wait("Greylib init failed!");
2539 return PLUGIN_ERROR;
2541 grey_setfont(FONT_UI);
2542 buf_size -= grey_buf_used;
2543 buf = (void*)(grey_buf_used + (char*)buf);
2544 #endif
2545 buflib_init(&buf_ctx, (void *)buf, buf_size);
2547 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2549 error_wait("Unable to load empty slide image");
2550 return PLUGIN_ERROR;
2553 if (!create_pf_thread()) {
2554 error_wait("Cannot create thread!");
2555 return PLUGIN_ERROR;
2558 int i;
2560 /* initialize */
2561 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2562 cache[i].hid = 0;
2563 cache[i].index = 0;
2564 cache[i].next = i + 1;
2565 cache[i].prev = i - 1;
2567 cache[0].prev = i - 1;
2568 cache[i - 1].next = 0;
2569 cache_free = 0;
2570 buffer = LCD_BUF;
2572 pf_state = pf_idle;
2574 track_index = -1;
2575 extra_fade = 0;
2576 slide_frame = 0;
2577 step = 0;
2578 target = 0;
2579 fade = 256;
2581 recalc_offsets();
2582 reset_slides();
2584 char fpstxt[10];
2585 int button;
2587 int frames = 0;
2588 long last_update = *rb->current_tick;
2589 long current_update;
2590 long update_interval = 100;
2591 int fps = 0;
2592 int fpstxt_y;
2594 bool instant_update;
2595 #ifdef USEGSLIB
2596 grey_show(true);
2597 grey_set_drawmode(DRMODE_FG);
2598 #endif
2599 rb->lcd_set_drawmode(DRMODE_FG);
2600 while (true) {
2601 current_update = *rb->current_tick;
2602 frames++;
2604 /* Initial rendering */
2605 instant_update = false;
2607 /* Handle states */
2608 switch ( pf_state ) {
2609 case pf_scrolling:
2610 update_scroll_animation();
2611 render_all_slides();
2612 instant_update = true;
2613 break;
2614 case pf_cover_in:
2615 update_cover_in_animation();
2616 render_all_slides();
2617 instant_update = true;
2618 break;
2619 case pf_cover_out:
2620 update_cover_out_animation();
2621 render_all_slides();
2622 instant_update = true;
2623 break;
2624 case pf_show_tracks:
2625 show_track_list();
2626 break;
2627 case pf_idle:
2628 render_all_slides();
2629 break;
2632 /* Calculate FPS */
2633 if (current_update - last_update > update_interval) {
2634 fps = frames * HZ / (current_update - last_update);
2635 last_update = current_update;
2636 frames = 0;
2638 /* Draw FPS */
2639 if (show_fps)
2641 #ifdef USEGSLIB
2642 MYLCD(set_foreground)(G_BRIGHT(255));
2643 #else
2644 MYLCD(set_foreground)(G_PIX(255,0,0));
2645 #endif
2646 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2647 if (show_album_name == album_name_top)
2648 fpstxt_y = LCD_HEIGHT -
2649 rb->screens[SCREEN_MAIN]->getcharheight();
2650 else
2651 fpstxt_y = 0;
2652 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2654 draw_album_text();
2657 /* Copy offscreen buffer to LCD and give time to other threads */
2658 MYLCD(update)();
2659 rb->yield();
2661 /*/ Handle buttons */
2662 button = rb->get_custom_action(CONTEXT_PLUGIN
2663 #ifndef USE_CORE_PREVNEXT
2664 |(pf_state == pf_show_tracks ? 1 : 0)
2665 #endif
2666 ,instant_update ? 0 : HZ/16,
2667 get_context_map);
2669 switch (button) {
2670 case PF_QUIT:
2671 return PLUGIN_OK;
2672 case PF_WPS:
2673 return PLUGIN_GOTO_WPS;
2674 case PF_BACK:
2675 if ( pf_state == pf_show_tracks )
2677 buflib_buffer_in(&buf_ctx, borrowed);
2678 borrowed = 0;
2679 track_index = -1;
2680 pf_state = pf_cover_out;
2682 if (pf_state == pf_idle || pf_state == pf_scrolling)
2683 return PLUGIN_OK;
2684 break;
2685 case PF_MENU:
2686 #ifdef USEGSLIB
2687 grey_show(false);
2688 #endif
2689 ret = main_menu();
2690 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2691 if ( ret == -1 ) return PLUGIN_OK;
2692 if ( ret != 0 ) return ret;
2693 #ifdef USEGSLIB
2694 grey_show(true);
2695 #endif
2696 MYLCD(set_drawmode)(DRMODE_FG);
2697 break;
2699 case PF_NEXT:
2700 case PF_NEXT_REPEAT:
2701 if ( pf_state == pf_show_tracks )
2702 select_next_track();
2703 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2704 show_next_slide();
2705 break;
2707 case PF_PREV:
2708 case PF_PREV_REPEAT:
2709 if ( pf_state == pf_show_tracks )
2710 select_prev_track();
2711 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2712 show_previous_slide();
2713 break;
2715 case PF_SELECT:
2716 if ( pf_state == pf_idle ) {
2717 pf_state = pf_cover_in;
2719 else if ( pf_state == pf_show_tracks ) {
2720 #if PF_PLAYBACK_CAPABLE
2721 start_playback();
2722 #endif
2724 break;
2725 default:
2726 if (rb->default_event_handler_ex(button, cleanup, NULL)
2727 == SYS_USB_CONNECTED)
2728 return PLUGIN_USB_CONNECTED;
2729 break;
2734 /*************************** Plugin entry point ****************************/
2736 enum plugin_status plugin_start(const void *parameter)
2738 int ret, i;
2739 (void) parameter;
2741 FOR_NB_SCREENS(i)
2742 rb->viewportmanager_theme_enable(i, false, NULL);
2743 /* Turn off backlight timeout */
2744 backlight_force_on(); /* backlight control in lib/helper.c */
2745 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2746 rb->cpu_boost(true);
2747 #endif
2748 #if PF_PLAYBACK_CAPABLE
2749 buf = rb->plugin_get_buffer(&buf_size);
2750 #else
2751 buf = rb->plugin_get_audio_buffer(&buf_size);
2752 #ifndef SIMULATOR
2753 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2755 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2756 buf_size = MIN(buf_size, tmp_size);
2758 #endif
2759 #endif
2760 ret = main();
2761 if ( ret == PLUGIN_OK ) {
2762 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2763 CONFIG_VERSION))
2765 rb->splash(HZ, "Error writing config.");
2766 ret = PLUGIN_ERROR;
2770 cleanup(NULL);
2771 return ret;