Fix red in bootloaders
[maemo-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob496e9c94dfac4b6b7110f632211459604b767cda
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_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
109 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
110 #endif
111 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
112 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
113 #elif CONFIG_KEYPAD == SANSA_C100_PAD
114 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
115 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
116 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
117 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
118 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
119 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
120 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
121 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
122 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
123 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
125 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
126 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
127 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
128 #if CONFIG_KEYPAD == COWOND2_PAD
129 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
130 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
131 #endif
132 #elif CONFIG_KEYPAD == SANSA_E200_PAD
133 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
134 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
135 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
136 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
137 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
138 || (CONFIG_KEYPAD == IPOD_4G_PAD)
139 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
140 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
141 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
142 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
143 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
144 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
145 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
146 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
147 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
148 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
149 #endif
150 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
151 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
152 #else
153 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
154 #endif
156 const struct button_mapping *pf_contexts[] =
158 #ifndef USE_CORE_PREVNEXT
159 pf_context_album_scroll,
160 #endif
161 pf_context_buttons
164 #if LCD_DEPTH < 8
165 #if LCD_DEPTH > 1
166 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
167 #else /* LCD_DEPTH <= 1 */
168 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
169 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
170 #define PICTUREFLOW_DRMODE DRMODE_SOLID
171 #else
172 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
173 #endif
174 #endif /* LCD_DEPTH <= 1 */
175 #define USEGSLIB
176 GREY_INFO_STRUCT
177 #define LCD_BUF _grey_info.buffer
178 #define MYLCD(fn) grey_ ## fn
179 #define G_PIX(r,g,b) \
180 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
181 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
182 #define G_BRIGHT(y) (y)
183 #define BUFFER_WIDTH _grey_info.width
184 #define BUFFER_HEIGHT _grey_info.height
185 typedef unsigned char pix_t;
186 #else /* LCD_DEPTH >= 8 */
187 #define LCD_BUF rb->lcd_framebuffer
188 #define MYLCD(fn) rb->lcd_ ## fn
189 #define G_PIX LCD_RGBPACK
190 #define N_PIX LCD_RGBPACK
191 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
192 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
193 #define BUFFER_WIDTH LCD_WIDTH
194 #define BUFFER_HEIGHT LCD_HEIGHT
195 typedef fb_data pix_t;
196 #endif /* LCD_DEPTH >= 8 */
198 /* for fixed-point arithmetic, we need minimum 32-bit long
199 long long (64-bit) might be useful for multiplication and division */
200 #define PFreal long
201 #define PFREAL_SHIFT 10
202 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
203 #define PFREAL_ONE (1 << PFREAL_SHIFT)
204 #define PFREAL_HALF (PFREAL_ONE >> 1)
207 #define IANGLE_MAX 1024
208 #define IANGLE_MASK 1023
210 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
211 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
212 #define DISPLAY_HEIGHT REFLECT_TOP
213 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
214 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
215 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
216 (REFLECT_HEIGHT * 5))
217 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
218 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
219 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
220 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
221 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
223 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
225 #define MAX_SLIDES_COUNT 10
227 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
228 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
230 #define EV_EXIT 9999
231 #define EV_WAKEUP 1337
233 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
234 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
235 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
237 /* Error return values */
238 #define ERROR_NO_ALBUMS -1
239 #define ERROR_BUFFER_FULL -2
241 /* current version for cover cache */
242 #define CACHE_VERSION 3
243 #define CONFIG_VERSION 1
244 #define CONFIG_FILE "pictureflow.cfg"
246 /** structs we use */
248 struct slide_data {
249 int slide_index;
250 int angle;
251 PFreal cx;
252 PFreal cy;
253 PFreal distance;
256 struct slide_cache {
257 int index; /* index of the cached slide */
258 int hid; /* handle ID of the cached slide */
259 short next; /* "next" slide, with LRU last */
260 short prev; /* "previous" slide */
263 struct album_data {
264 int name_idx;
265 long seek;
268 struct track_data {
269 uint32_t sort;
270 int name_idx; /* offset to the track name */
271 long seek;
272 #if PF_PLAYBACK_CAPABLE
273 /* offset to the filename in the string, needed for playlist generation */
274 int filename_idx;
275 #endif
278 struct rect {
279 int left;
280 int right;
281 int top;
282 int bottom;
285 struct load_slide_event_data {
286 int slide_index;
287 int cache_index;
291 struct pfraw_header {
292 int32_t width; /* bmap width in pixels */
293 int32_t height; /* bmap height in pixels */
296 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
297 album_name_top };
298 static char* show_album_name_conf[] =
300 "hide",
301 "bottom",
302 "top"
305 #define MAX_SPACING 40
306 #define MAX_MARGIN 80
308 /* config values and their defaults */
309 static int slide_spacing = DISPLAY_WIDTH / 4;
310 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
311 static int num_slides = 4;
312 static int zoom = 100;
313 static bool show_fps = false;
314 static bool resize = true;
315 static int cache_version = 0;
316 static int show_album_name = (LCD_HEIGHT > 100)
317 ? album_name_top : album_name_bottom;
319 static struct configdata config[] =
321 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
322 NULL },
323 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
324 NULL },
325 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
326 NULL },
327 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
328 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
329 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
330 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
331 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
332 show_album_name_conf }
335 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
337 /** below we allocate the memory we want to use **/
339 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
340 static uint8_t reflect_table[REFLECT_HEIGHT];
341 static struct slide_data center_slide;
342 static struct slide_data left_slides[MAX_SLIDES_COUNT];
343 static struct slide_data right_slides[MAX_SLIDES_COUNT];
344 static int slide_frame;
345 static int step;
346 static int target;
347 static int fade;
348 static int center_index = 0; /* index of the slide that is in the center */
349 static int itilt;
350 static PFreal offsetX;
351 static PFreal offsetY;
352 static int number_of_slides;
354 static struct slide_cache cache[SLIDE_CACHE_SIZE];
355 static int cache_free;
356 static int cache_used = -1;
357 static int cache_left_index = -1;
358 static int cache_right_index = -1;
359 static int cache_center_index = -1;
361 /* use long for aligning */
362 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
363 /* queue (as array) for scheduling load_surface */
365 static int empty_slide_hid;
367 unsigned int thread_id;
368 struct event_queue thread_q;
370 static struct tagcache_search tcs;
372 static struct buflib_context buf_ctx;
374 static struct album_data *album;
375 static char *album_names;
376 static int album_count;
378 static struct track_data *tracks;
379 static char *track_names;
380 static size_t borrowed = 0;
381 static int track_count;
382 static int track_index;
383 static int selected_track;
384 static int selected_track_pulse;
385 void reset_track_list(void);
387 void * buf;
388 size_t buf_size;
390 static bool thread_is_running;
392 static int cover_animation_keyframe;
393 static int extra_fade;
395 static int albumtxt_x = 0;
396 static int albumtxt_dir = -1;
397 static int prev_center_index = -1;
399 static int start_index_track_list = 0;
400 static int track_list_visible_entries = 0;
401 static int track_list_y;
402 static int track_list_h;
403 static int track_scroll_index = 0;
404 static int track_scroll_dir = 1;
407 Proposals for transitions:
409 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
410 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
412 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
414 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
416 TODO:
417 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
418 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
420 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
422 enum pf_states {
423 pf_idle = 0,
424 pf_scrolling,
425 pf_cover_in,
426 pf_show_tracks,
427 pf_cover_out
430 static int pf_state;
432 /** code */
433 static bool free_slide_prio(int prio);
434 static inline unsigned fade_color(pix_t c, unsigned a);
435 bool save_pfraw(char* filename, struct bitmap *bm);
436 bool load_new_slide(void);
437 int load_surface(int);
439 static inline PFreal fmul(PFreal a, PFreal b)
441 return (a*b) >> PFREAL_SHIFT;
445 * This version preshifts each operand, which is useful when we know how many
446 * of the least significant bits will be empty, or are worried about overflow
447 * in a particular calculation
449 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
451 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
454 /* ARMv5+ has a clz instruction equivalent to our function.
456 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
457 static inline int clz(uint32_t v)
459 return __builtin_clz(v);
462 /* Otherwise, use our clz, which can be inlined */
463 #elif defined(CPU_COLDFIRE)
464 /* This clz is based on the log2(n) implementation at
465 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
466 * A clz benchmark plugin showed this to be about 14% faster on coldfire
467 * than the LUT-based version.
469 static inline int clz(uint32_t v)
471 int r = 32;
472 if (v >= 0x10000)
474 v >>= 16;
475 r -= 16;
477 if (v & 0xff00)
479 v >>= 8;
480 r -= 8;
482 if (v & 0xf0)
484 v >>= 4;
485 r -= 4;
487 if (v & 0xc)
489 v >>= 2;
490 r -= 2;
492 if (v & 2)
494 v >>= 1;
495 r -= 1;
497 r -= v;
498 return r;
500 #else
501 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
502 0, 0, 0, 0, 0, 0, 0, 0 };
503 /* This clz is based on the log2(n) implementation at
504 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
505 * It is not any faster than the one above, but trades 16B in the lookup table
506 * for a savings of 12B per each inlined call.
508 static inline int clz(uint32_t v)
510 int r = 28;
511 if (v >= 0x10000)
513 v >>= 16;
514 r -= 16;
516 if (v & 0xff00)
518 v >>= 8;
519 r -= 8;
521 if (v & 0xf0)
523 v >>= 4;
524 r -= 4;
526 return r + clz_lut[v];
528 #endif
530 /* Return the maximum possible left shift for a signed int32, without
531 * overflow
533 static inline int allowed_shift(int32_t val)
535 uint32_t uval = val ^ (val >> 31);
536 return clz(uval) - 1;
539 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
540 * num and den before dividing.
542 static inline PFreal fdiv(PFreal num, PFreal den)
544 int shift = allowed_shift(num);
545 shift = MIN(PFREAL_SHIFT, shift);
546 num <<= shift;
547 den >>= PFREAL_SHIFT - shift;
548 return num / den;
551 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
552 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
553 #define fabs(a) (a < 0 ? -a : a)
554 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
556 #if CONFIG_CPU == SH7034
557 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
558 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
559 #else
560 #define MULUQ(a, b) ((a) * (b))
561 #endif
564 #if 0
565 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
566 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
568 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
569 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
571 static inline PFreal fmul(PFreal a, PFreal b)
573 return (a*b) >> PFREAL_SHIFT;
576 static inline PFreal fdiv(PFreal n, PFreal m)
578 return (n<<(PFREAL_SHIFT))/m;
580 #endif
582 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
583 static const short sin_tab[] = {
584 0, 100, 200, 297, 392, 483, 569, 650,
585 724, 792, 851, 903, 946, 980, 1004, 1019,
586 1024, 1019, 1004, 980, 946, 903, 851, 792,
587 724, 650, 569, 483, 392, 297, 200, 100,
588 0, -100, -200, -297, -392, -483, -569, -650,
589 -724, -792, -851, -903, -946, -980, -1004, -1019,
590 -1024, -1019, -1004, -980, -946, -903, -851, -792,
591 -724, -650, -569, -483, -392, -297, -200, -100,
595 static inline PFreal fsin(int iangle)
597 iangle &= IANGLE_MASK;
599 int i = (iangle >> 4);
600 PFreal p = sin_tab[i];
601 PFreal q = sin_tab[(i+1)];
602 PFreal g = (q - p);
603 return p + g * (iangle-i*16)/16;
606 static inline PFreal fcos(int iangle)
608 return fsin(iangle + (IANGLE_MAX >> 2));
611 static inline unsigned scale_val(unsigned val, unsigned bits)
613 val = val * ((1 << bits) - 1);
614 return ((val >> 8) + val + 128) >> 8;
617 static void output_row_8_transposed(uint32_t row, void * row_in,
618 struct scaler_context *ctx)
620 pix_t *dest = (pix_t*)ctx->bm->data + row;
621 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
622 #ifdef USEGSLIB
623 uint8_t *qp = (uint8_t*)row_in;
624 for (; dest < end; dest += ctx->bm->height)
625 *dest = *qp++;
626 #else
627 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
628 unsigned r, g, b;
629 for (; dest < end; dest += ctx->bm->height)
631 r = scale_val(qp->red, 5);
632 g = scale_val(qp->green, 6);
633 b = scale_val((qp++)->blue, 5);
634 *dest = LCD_RGBPACK_LCD(r,g,b);
636 #endif
639 static void output_row_32_transposed(uint32_t row, void * row_in,
640 struct scaler_context *ctx)
642 pix_t *dest = (pix_t*)ctx->bm->data + row;
643 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
644 #ifdef USEGSLIB
645 uint32_t *qp = (uint32_t*)row_in;
646 for (; dest < end; dest += ctx->bm->height)
647 *dest = SC_OUT(*qp++, ctx);
648 #else
649 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
650 int r, g, b;
651 for (; dest < end; dest += ctx->bm->height)
653 r = scale_val(SC_OUT(qp->r, ctx), 5);
654 g = scale_val(SC_OUT(qp->g, ctx), 6);
655 b = scale_val(SC_OUT(qp->b, ctx), 5);
656 qp++;
657 *dest = LCD_RGBPACK_LCD(r,g,b);
659 #endif
662 #ifdef HAVE_LCD_COLOR
663 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
664 struct scaler_context *ctx)
666 pix_t *dest = (pix_t*)ctx->bm->data + row;
667 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
668 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
669 for (; dest < end; dest += ctx->bm->height)
671 unsigned r, g, b, y, u, v;
672 y = SC_OUT(qp->b, ctx);
673 u = SC_OUT(qp->g, ctx);
674 v = SC_OUT(qp->r, ctx);
675 qp++;
676 yuv_to_rgb(y, u, v, &r, &g, &b);
677 r = scale_val(r, 5);
678 g = scale_val(g, 6);
679 b = scale_val(b, 5);
680 *dest = LCD_RGBPACK_LCD(r, g, b);
683 #endif
685 static unsigned int get_size(struct bitmap *bm)
687 return bm->width * bm->height * sizeof(pix_t);
690 const struct custom_format format_transposed = {
691 .output_row_8 = output_row_8_transposed,
692 #ifdef HAVE_LCD_COLOR
693 .output_row_32 = {
694 output_row_32_transposed,
695 output_row_32_transposed_fromyuv
697 #else
698 .output_row_32 = output_row_32_transposed,
699 #endif
700 .get_size = get_size
703 static const struct button_mapping* get_context_map(int context)
705 return pf_contexts[context & ~CONTEXT_PLUGIN];
708 /* Create the lookup table with the scaling values for the reflections */
709 void init_reflect_table(void)
711 int i;
712 for (i = 0; i < REFLECT_HEIGHT; i++)
713 reflect_table[i] =
714 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
715 (5 * REFLECT_HEIGHT);
719 Create an index of all albums from the database.
720 Also store the album names so we can access them later.
722 int create_album_index(void)
724 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
725 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
726 album_count = 0;
727 rb->tagcache_search(&tcs, tag_album);
728 unsigned int l, old_l = 0;
729 album_names = buf;
730 album[0].name_idx = 0;
731 while (rb->tagcache_get_next(&tcs))
733 buf_size -= sizeof(struct album_data);
734 l = tcs.result_len;
735 if ( album_count > 0 )
736 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
738 if ( l > buf_size )
739 /* not enough memory */
740 return ERROR_BUFFER_FULL;
742 rb->strcpy(buf, tcs.result);
743 buf_size -= l;
744 buf = l + (char *)buf;
745 album[-album_count].seek = tcs.result_seek;
746 old_l = l;
747 album_count++;
749 rb->tagcache_search_finish(&tcs);
750 ALIGN_BUFFER(buf, buf_size, 4);
751 int i;
752 struct album_data* tmp_album = (struct album_data*)buf;
753 for (i = album_count - 1; i >= 0; i--)
754 tmp_album[i] = album[-i];
755 album = tmp_album;
756 buf = album + album_count;
757 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
761 Return a pointer to the album name of the given slide_index
763 char* get_album_name(const int slide_index)
765 return album_names + album[slide_index].name_idx;
769 Return a pointer to the track name of the active album
770 create_track_index has to be called first.
772 char* get_track_name(const int track_index)
774 if ( track_index < track_count )
775 return track_names + tracks[track_index].name_idx;
776 return 0;
778 #if PF_PLAYBACK_CAPABLE
779 char* get_track_filename(const int track_index)
781 if ( track_index < track_count )
782 return track_names + tracks[track_index].filename_idx;
783 return 0;
785 #endif
787 Compare two unsigned ints passed via pointers.
789 int compare_tracks (const void *a_v, const void *b_v)
791 uint32_t a = ((struct track_data *)a_v)->sort;
792 uint32_t b = ((struct track_data *)b_v)->sort;
793 return (int)(a - b);
797 Create the track index of the given slide_index.
799 void create_track_index(const int slide_index)
801 if ( slide_index == track_index )
802 return;
803 track_index = slide_index;
805 if (!rb->tagcache_search(&tcs, tag_title))
806 goto fail;
808 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
809 track_count=0;
810 int string_index = 0, track_num;
811 int disc_num;
812 size_t out = 0;
813 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
814 borrowed += out;
815 int avail = borrowed;
816 tracks = (struct track_data*)(track_names + borrowed);
817 while (rb->tagcache_get_next(&tcs))
819 int len = 0, fn_idx = 0;
821 avail -= sizeof(struct track_data);
822 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
823 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
825 if (disc_num < 0)
826 disc_num = 0;
827 retry:
828 if (track_num >= 0)
830 if (disc_num)
831 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
832 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
833 else
834 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
835 "%d: %s", track_num + 1, tcs.result);
837 else
839 track_num = 0;
840 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
841 "%s", tcs.result);
843 if (fn_idx <= 0)
844 goto fail;
845 #if PF_PLAYBACK_CAPABLE
846 int remain = avail - fn_idx;
847 if (remain >= MAX_PATH)
848 { /* retrieve filename for building the playlist */
849 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
850 track_names + string_index + fn_idx, remain);
851 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
852 /* make sure track name and file name are really split by a \0, else
853 * get_track_name might fail */
854 *(track_names + string_index + fn_idx -1) = '\0';
857 else /* request more buffer so that track and filename fit */
858 len = (avail - remain) + MAX_PATH;
859 #else
860 len = fn_idx;
861 #endif
862 if (len > avail)
864 while (len > avail)
866 if (!free_slide_prio(0))
867 goto fail;
868 out = 0;
869 buflib_buffer_out(&buf_ctx, &out);
870 avail += out;
871 borrowed += out;
872 if (track_count)
874 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
875 unsigned int bytes = track_count * sizeof(struct track_data);
876 rb->memmove(new_tracks, tracks, bytes);
877 tracks = new_tracks;
880 goto retry;
883 avail -= len;
884 tracks--;
885 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
886 tracks->name_idx = string_index;
887 tracks->seek = tcs.result_seek;
888 #if PF_PLAYBACK_CAPABLE
889 tracks->filename_idx = fn_idx + string_index;
890 #endif
891 track_count++;
892 string_index += len;
895 rb->tagcache_search_finish(&tcs);
897 /* now fix the track list order */
898 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
899 return;
900 fail:
901 track_count = 0;
902 return;
906 Determine filename of the album art for the given slide_index and
907 store the result in buf.
908 The algorithm looks for the first track of the given album uses
909 find_albumart to find the filename.
911 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
912 int buflen)
914 if ( slide_index == -1 )
916 rb->strncpy( buf, EMPTY_SLIDE, buflen );
919 if (!rb->tagcache_search(&tcs, tag_filename))
920 return false;
922 bool result;
923 /* find the first track of the album */
924 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
926 if ( rb->tagcache_get_next(&tcs) ) {
927 struct mp3entry id3;
928 int fd;
930 #ifdef HAVE_TC_RAMCACHE
931 if (rb->tagcache_fill_tags(&id3, tcs.result))
933 rb->strncpy(id3.path, tcs.result, sizeof(id3.path));
934 id3.path[sizeof(id3.path) - 1] = 0;
936 else
937 #endif
939 fd = rb->open(tcs.result, O_RDONLY);
940 rb->get_metadata(&id3, fd, tcs.result);
941 rb->close(fd);
943 if ( search_albumart_files(&id3, ":", buf, buflen) )
944 result = true;
945 else
946 result = false;
948 else {
949 /* did not find a matching track */
950 result = false;
952 rb->tagcache_search_finish(&tcs);
953 return result;
957 Draw the PictureFlow logo
959 void draw_splashscreen(void)
961 unsigned char * buf_tmp = buf;
962 size_t buf_tmp_size = buf_size;
963 struct screen* display = rb->screens[0];
964 #if FB_DATA_SZ > 1
965 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
966 #endif
967 struct bitmap logo = {
968 #if LCD_WIDTH < 200
969 .width = 100,
970 .height = 18,
971 #else
972 .width = 193,
973 .height = 34,
974 #endif
975 .data = buf_tmp
977 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
978 NULL);
979 #if LCD_DEPTH > 1
980 rb->lcd_set_background(N_BRIGHT(0));
981 rb->lcd_set_foreground(N_BRIGHT(255));
982 #else
983 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
984 #endif
985 rb->lcd_clear_display();
987 if (ret > 0)
989 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
990 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
991 #endif
992 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
993 logo.width, logo.height);
994 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
995 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
996 #endif
999 rb->lcd_update();
1004 Draw a simple progress bar
1006 void draw_progressbar(int step)
1008 int txt_w, txt_h;
1009 const int bar_height = 22;
1010 const int w = LCD_WIDTH - 20;
1011 const int x = 10;
1013 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1015 int y = (LCD_HEIGHT - txt_h)/2;
1017 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1018 y += (txt_h + 5);
1020 #if LCD_DEPTH > 1
1021 rb->lcd_set_foreground(N_BRIGHT(100));
1022 #endif
1023 rb->lcd_drawrect(x, y, w+2, bar_height);
1024 #if LCD_DEPTH > 1
1025 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1026 #endif
1028 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1029 #if LCD_DEPTH > 1
1030 rb->lcd_set_foreground(N_BRIGHT(255));
1031 #endif
1032 rb->lcd_update();
1033 rb->yield();
1037 Precomupte the album art images and store them in CACHE_PREFIX.
1039 bool create_albumart_cache(void)
1041 int ret;
1043 int i, slides = 0;
1044 struct bitmap input_bmp;
1046 char pfraw_file[MAX_PATH];
1047 char albumart_file[MAX_PATH];
1048 unsigned int format = FORMAT_NATIVE;
1049 cache_version = 0;
1050 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1051 if (resize)
1052 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1053 for (i=0; i < album_count; i++)
1055 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1057 /* delete existing cache, so it's a true rebuild */
1058 if(rb->file_exists(pfraw_file))
1059 rb->remove(pfraw_file);
1060 draw_progressbar(i);
1061 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1062 continue;
1064 input_bmp.data = buf;
1065 input_bmp.width = DISPLAY_WIDTH;
1066 input_bmp.height = DISPLAY_HEIGHT;
1067 ret = read_image_file(albumart_file, &input_bmp,
1068 buf_size, format, &format_transposed);
1069 if (ret <= 0) {
1070 rb->splash(HZ, "Could not read bmp");
1071 continue; /* skip missing/broken files */
1073 if (!save_pfraw(pfraw_file, &input_bmp))
1075 rb->splash(HZ, "Could not write bmp");
1077 slides++;
1078 if ( rb->button_get(false) == PF_MENU ) return false;
1080 if ( slides == 0 ) {
1081 /* Warn the user that we couldn't find any albumart */
1082 rb->splash(2*HZ, "No album art found");
1083 return false;
1085 return true;
1089 Thread used for loading and preparing bitmaps in the background
1091 void thread(void)
1093 long sleep_time = 5 * HZ;
1094 struct queue_event ev;
1095 while (1) {
1096 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1097 switch (ev.id) {
1098 case EV_EXIT:
1099 return;
1100 case EV_WAKEUP:
1101 /* we just woke up */
1102 break;
1104 while ( load_new_slide() ) {
1105 rb->yield();
1106 switch (ev.id) {
1107 case EV_EXIT:
1108 return;
1116 End the thread by posting the EV_EXIT event
1118 void end_pf_thread(void)
1120 if ( thread_is_running ) {
1121 rb->queue_post(&thread_q, EV_EXIT, 0);
1122 rb->thread_wait(thread_id);
1123 /* remove the thread's queue from the broadcast list */
1124 rb->queue_delete(&thread_q);
1125 thread_is_running = false;
1132 Create the thread an setup the event queue
1134 bool create_pf_thread(void)
1136 /* put the thread's queue in the bcast list */
1137 rb->queue_init(&thread_q, true);
1138 if ((thread_id = rb->create_thread(
1139 thread,
1140 thread_stack,
1141 sizeof(thread_stack),
1143 "Picture load thread"
1144 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1145 PRIORITY_REALTIME + 1))
1146 IF_COP(, CPU)
1148 ) == 0) {
1149 return false;
1151 thread_is_running = true;
1152 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1153 return true;
1157 Safe the given bitmap as filename in the pfraw format
1159 bool save_pfraw(char* filename, struct bitmap *bm)
1161 struct pfraw_header bmph;
1162 bmph.width = bm->width;
1163 bmph.height = bm->height;
1164 int fh = rb->creat( filename );
1165 if( fh < 0 ) return false;
1166 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1167 int y;
1168 for( y = 0; y < bm->height; y++ )
1170 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1171 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1173 rb->close( fh );
1174 return true;
1179 * The following functions implement the linked-list-in-array used to manage
1180 * the LRU cache of slides, and the list of free cache slots.
1183 #define seek_right_while(start, cond) \
1184 ({ \
1185 int ind_, next_ = (start); \
1186 do { \
1187 ind_ = next_; \
1188 next_ = cache[ind_].next; \
1189 } while (next_ != cache_used && (cond)); \
1190 ind_; \
1193 #define seek_left_while(start, cond) \
1194 ({ \
1195 int ind_, next_ = (start); \
1196 do { \
1197 ind_ = next_; \
1198 next_ = cache[ind_].prev; \
1199 } while (ind_ != cache_used && (cond)); \
1200 ind_; \
1204 Pop the given item from the linked list starting at *head, returning the next
1205 item, or -1 if the list is now empty.
1207 static inline int lla_pop_item (int *head, int i)
1209 int prev = cache[i].prev;
1210 int next = cache[i].next;
1211 if (i == next)
1213 *head = -1;
1214 return -1;
1216 else if (i == *head)
1217 *head = next;
1218 cache[next].prev = prev;
1219 cache[prev].next = next;
1220 return next;
1225 Pop the head item from the list starting at *head, returning the index of the
1226 item, or -1 if the list is already empty.
1228 static inline int lla_pop_head (int *head)
1230 int i = *head;
1231 if (i != -1)
1232 lla_pop_item(head, i);
1233 return i;
1237 Insert the item at index i before the one at index p.
1239 static inline void lla_insert (int i, int p)
1241 int next = p;
1242 int prev = cache[next].prev;
1243 cache[next].prev = i;
1244 cache[prev].next = i;
1245 cache[i].next = next;
1246 cache[i].prev = prev;
1251 Insert the item at index i at the end of the list starting at *head.
1253 static inline void lla_insert_tail (int *head, int i)
1255 if (*head == -1)
1257 *head = i;
1258 cache[i].next = i;
1259 cache[i].prev = i;
1260 } else
1261 lla_insert(i, *head);
1265 Insert the item at index i before the one at index p.
1267 static inline void lla_insert_after(int i, int p)
1269 p = cache[p].next;
1270 lla_insert(i, p);
1275 Insert the item at index i before the one at index p in the list starting at
1276 *head
1278 static inline void lla_insert_before(int *head, int i, int p)
1280 lla_insert(i, p);
1281 if (*head == p)
1282 *head = i;
1287 Free the used slide at index i, and its buffer, and move it to the free
1288 slides list.
1290 static inline void free_slide(int i)
1292 if (cache[i].hid != empty_slide_hid)
1293 buflib_free(&buf_ctx, cache[i].hid);
1294 cache[i].index = -1;
1295 lla_pop_item(&cache_used, i);
1296 lla_insert_tail(&cache_free, i);
1297 if (cache_used == -1)
1299 cache_right_index = -1;
1300 cache_left_index = -1;
1301 cache_center_index = -1;
1307 Free one slide ranked above the given priority. If no such slide can be found,
1308 return false.
1310 static bool free_slide_prio(int prio)
1312 if (cache_used == -1)
1313 return false;
1314 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1315 int prio_l = cache[l].index < center_index ?
1316 center_index - cache[l].index : 0;
1317 int prio_r = cache[r].index > center_index ?
1318 cache[r].index - center_index : 0;
1319 if (prio_l > prio_r)
1321 i = l;
1322 prio_max = prio_l;
1323 } else {
1324 i = r;
1325 prio_max = prio_r;
1327 if (prio_max > prio)
1329 if (i == cache_left_index)
1330 cache_left_index = cache[i].next;
1331 if (i == cache_right_index)
1332 cache_right_index = cache[i].prev;
1333 free_slide(i);
1334 return true;
1335 } else
1336 return false;
1340 Read the pfraw image given as filename and return the hid of the buffer
1342 int read_pfraw(char* filename, int prio)
1344 struct pfraw_header bmph;
1345 int fh = rb->open(filename, O_RDONLY);
1346 if( fh < 0 )
1347 return empty_slide_hid;
1348 else
1349 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1351 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1352 bmph.width * bmph.height;
1354 int hid;
1355 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1357 if (!hid) {
1358 rb->close( fh );
1359 return 0;
1362 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1364 bm->width = bmph.width;
1365 bm->height = bmph.height;
1366 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1368 int y;
1369 for( y = 0; y < bm->height; y++ )
1371 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1372 data += bm->width;
1374 rb->close( fh );
1375 return hid;
1380 Load the surface for the given slide_index into the cache at cache_index.
1382 static inline bool load_and_prepare_surface(const int slide_index,
1383 const int cache_index,
1384 const int prio)
1386 char tmp_path_name[MAX_PATH+1];
1387 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1388 slide_index);
1390 int hid = read_pfraw(tmp_path_name, prio);
1391 if (!hid)
1392 return false;
1394 cache[cache_index].hid = hid;
1396 if ( cache_index < SLIDE_CACHE_SIZE ) {
1397 cache[cache_index].index = slide_index;
1400 return true;
1405 Load the "next" slide that we can load, freeing old slides if needed, provided
1406 that they are further from center_index than the current slide
1408 bool load_new_slide(void)
1410 int i = -1;
1411 if (cache_center_index != -1)
1413 int next, prev;
1414 if (cache[cache_center_index].index != center_index)
1416 if (cache[cache_center_index].index < center_index)
1418 cache_center_index = seek_right_while(cache_center_index,
1419 cache[next_].index <= center_index);
1420 prev = cache_center_index;
1421 next = cache[cache_center_index].next;
1423 else
1425 cache_center_index = seek_left_while(cache_center_index,
1426 cache[next_].index >= center_index);
1427 next = cache_center_index;
1428 prev = cache[cache_center_index].prev;
1430 if (cache[cache_center_index].index != center_index)
1432 if (cache_free == -1)
1433 free_slide_prio(0);
1434 i = lla_pop_head(&cache_free);
1435 if (!load_and_prepare_surface(center_index, i, 0))
1436 goto fail_and_refree;
1437 if (cache[next].index == -1)
1439 if (cache[prev].index == -1)
1440 goto insert_first_slide;
1441 else
1442 next = cache[prev].next;
1444 lla_insert(i, next);
1445 if (cache[i].index < cache[cache_used].index)
1446 cache_used = i;
1447 cache_center_index = i;
1448 cache_left_index = i;
1449 cache_right_index = i;
1450 return true;
1453 if (cache[cache_left_index].index >
1454 cache[cache_center_index].index)
1455 cache_left_index = cache_center_index;
1456 if (cache[cache_right_index].index <
1457 cache[cache_center_index].index)
1458 cache_right_index = cache_center_index;
1459 cache_left_index = seek_left_while(cache_left_index,
1460 cache[ind_].index - 1 == cache[next_].index);
1461 cache_right_index = seek_right_while(cache_right_index,
1462 cache[ind_].index - 1 == cache[next_].index);
1463 int prio_l = cache[cache_center_index].index -
1464 cache[cache_left_index].index + 1;
1465 int prio_r = cache[cache_right_index].index -
1466 cache[cache_center_index].index + 1;
1467 if ((prio_l < prio_r ||
1468 cache[cache_right_index].index >= number_of_slides) &&
1469 cache[cache_left_index].index > 0)
1471 if (cache_free == -1 && !free_slide_prio(prio_l))
1472 return false;
1473 i = lla_pop_head(&cache_free);
1474 if (load_and_prepare_surface(cache[cache_left_index].index
1475 - 1, i, prio_l))
1477 lla_insert_before(&cache_used, i, cache_left_index);
1478 cache_left_index = i;
1479 return true;
1481 } else if(cache[cache_right_index].index < number_of_slides - 1)
1483 if (cache_free == -1 && !free_slide_prio(prio_r))
1484 return false;
1485 i = lla_pop_head(&cache_free);
1486 if (load_and_prepare_surface(cache[cache_right_index].index
1487 + 1, i, prio_r))
1489 lla_insert_after(i, cache_right_index);
1490 cache_right_index = i;
1491 return true;
1494 } else {
1495 i = lla_pop_head(&cache_free);
1496 if (load_and_prepare_surface(center_index, i, 0))
1498 insert_first_slide:
1499 cache[i].next = i;
1500 cache[i].prev = i;
1501 cache_center_index = i;
1502 cache_left_index = i;
1503 cache_right_index = i;
1504 cache_used = i;
1505 return true;
1508 fail_and_refree:
1509 if (i != -1)
1511 lla_insert_tail(&cache_free, i);
1513 return false;
1518 Get a slide from the buffer
1520 static inline struct dim *get_slide(const int hid)
1522 if (!hid)
1523 return NULL;
1525 struct dim *bmp;
1527 bmp = buflib_get_data(&buf_ctx, hid);
1529 return bmp;
1534 Return the requested surface
1536 static inline struct dim *surface(const int slide_index)
1538 if (slide_index < 0)
1539 return 0;
1540 if (slide_index >= number_of_slides)
1541 return 0;
1542 int i;
1543 if ((i = cache_used ) != -1)
1545 do {
1546 if (cache[i].index == slide_index)
1547 return get_slide(cache[i].hid);
1548 i = cache[i].next;
1549 } while (i != cache_used);
1551 return get_slide(empty_slide_hid);
1555 adjust slides so that they are in "steady state" position
1557 void reset_slides(void)
1559 center_slide.angle = 0;
1560 center_slide.cx = 0;
1561 center_slide.cy = 0;
1562 center_slide.distance = 0;
1563 center_slide.slide_index = center_index;
1565 int i;
1566 for (i = 0; i < num_slides; i++) {
1567 struct slide_data *si = &left_slides[i];
1568 si->angle = itilt;
1569 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1570 si->cy = offsetY;
1571 si->slide_index = center_index - 1 - i;
1572 si->distance = 0;
1575 for (i = 0; i < num_slides; i++) {
1576 struct slide_data *si = &right_slides[i];
1577 si->angle = -itilt;
1578 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1579 si->cy = offsetY;
1580 si->slide_index = center_index + 1 + i;
1581 si->distance = 0;
1587 Updates look-up table and other stuff necessary for the rendering.
1588 Call this when the viewport size or slide dimension is changed.
1590 * To calculate the offset that will provide the proper margin, we use the same
1591 * projection used to render the slides. The solution for xc, the slide center,
1592 * is:
1593 * xp * (zo + xs * sin(r))
1594 * xc = xp - xs * cos(r) + ───────────────────────
1596 * TODO: support moving the side slides toward or away from the camera
1598 void recalc_offsets(void)
1600 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1601 PFreal zo;
1602 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1603 PFREAL_ONE) * zoom / 100;
1604 PFreal cosr, sinr;
1606 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1607 cosr = fcos(-itilt);
1608 sinr = fsin(-itilt);
1609 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1610 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1611 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1612 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1613 / CAM_DIST;
1614 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1619 Fade the given color by spreading the fb_data (ushort)
1620 to an uint, multiply and compress the result back to a ushort.
1622 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1623 static inline unsigned fade_color(pix_t c, unsigned a)
1625 unsigned int result;
1626 c = swap16(c);
1627 a = (a + 2) & 0x1fc;
1628 result = ((c & 0xf81f) * a) & 0xf81f00;
1629 result |= ((c & 0x7e0) * a) & 0x7e000;
1630 result >>= 8;
1631 return swap16(result);
1633 #elif LCD_PIXELFORMAT == RGB565
1634 static inline unsigned fade_color(pix_t c, unsigned a)
1636 unsigned int result;
1637 a = (a + 2) & 0x1fc;
1638 result = ((c & 0xf81f) * a) & 0xf81f00;
1639 result |= ((c & 0x7e0) * a) & 0x7e000;
1640 result >>= 8;
1641 return result;
1643 #else
1644 static inline unsigned fade_color(pix_t c, unsigned a)
1646 unsigned val = c;
1647 return MULUQ(val, a) >> 8;
1649 #endif
1652 * Render a single slide
1653 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1654 * on the slide from its center, zo is the slide's depth offset from the plane
1655 * of the display, r is the angle at which the slide is tilted, and xp is the
1656 * point on the display corresponding to xs on the slide, the projection
1657 * formulas are:
1659 * z * (xc + xs * cos(r))
1660 * xp = ──────────────────────
1661 * z + zo + xs * sin(r)
1663 * z * (xc - xp) - xp * zo
1664 * xs = ────────────────────────
1665 * xp * sin(r) - z * cos(r)
1667 * We use the xp projection once, to find the left edge of the slide on the
1668 * display. From there, we use the xs reverse projection to find the horizontal
1669 * offset from the slide center of each column on the screen, until we reach
1670 * the right edge of the slide, or the screen. The reverse projection can be
1671 * optimized by saving the numerator and denominator of the fraction, which can
1672 * then be incremented by (z + zo) and sin(r) respectively.
1674 void render_slide(struct slide_data *slide, const int alpha)
1676 struct dim *bmp = surface(slide->slide_index);
1677 if (!bmp) {
1678 return;
1680 if (slide->angle > 255 || slide->angle < -255)
1681 return;
1682 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1684 const int sw = bmp->width;
1685 const int sh = bmp->height;
1686 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1687 const int w = LCD_WIDTH;
1689 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1691 if (alpha == 256) { /* opaque -> copy table */
1692 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1693 } else { /* precalculate faded table */
1694 int i, lalpha;
1695 for (i = 0; i < REFLECT_HEIGHT; i++) {
1696 lalpha = reflect_table[i];
1697 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1701 PFreal cosr = fcos(slide->angle);
1702 PFreal sinr = fsin(slide->angle);
1703 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1704 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1705 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1706 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1707 (CAM_DIST_R + zo + fmul(xs,sinr)));
1709 /* Since we're finding the screen position of the left edge of the slide,
1710 * we round up.
1712 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1713 >> PFREAL_SHIFT;
1714 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1715 if (xi >= w) {
1716 return;
1718 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1719 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1720 xs = fdiv(xsnum, xsden);
1722 xsnumi = -CAM_DIST_R - zo;
1723 xsdeni = sinr;
1724 int x;
1725 int dy = PFREAL_ONE;
1726 for (x = xi; x < w; x++) {
1727 int column = (xs - slide_left) / PFREAL_ONE;
1728 if (column >= sw)
1729 break;
1730 if (zo || slide->angle)
1731 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1733 const pix_t *ptr = &src[column * bmp->height];
1734 const int pixelstep = BUFFER_WIDTH;
1736 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1737 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1738 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1740 if (alpha == 256) {
1741 while (p >= plim) {
1742 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1743 p -= dy;
1744 pixel -= pixelstep;
1746 } else {
1747 while (p >= plim) {
1748 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1749 p -= dy;
1750 pixel -= pixelstep;
1753 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1754 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1755 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1756 p + (LCD_HEIGHT/2) * dy);
1757 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1759 if (alpha == 256) {
1760 while (p < plim) {
1761 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1762 p += dy;
1763 pixel += pixelstep;
1765 } else {
1766 while (p < plim) {
1767 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1768 p += dy;
1769 pixel += pixelstep;
1772 while (p < plim2) {
1773 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1774 int lalpha = reftab[ty];
1775 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1776 p += dy;
1777 pixel += pixelstep;
1780 if (zo || slide->angle)
1782 xsnum += xsnumi;
1783 xsden += xsdeni;
1784 xs = fdiv(xsnum, xsden);
1785 } else
1786 xs += PFREAL_ONE;
1789 /* let the music play... */
1790 rb->yield();
1791 return;
1796 Jump the the given slide_index
1798 static inline void set_current_slide(const int slide_index)
1800 int old_center_index = center_index;
1801 step = 0;
1802 center_index = fbound(slide_index, 0, number_of_slides - 1);
1803 if (old_center_index != center_index)
1804 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1805 target = center_index;
1806 slide_frame = slide_index << 16;
1807 reset_slides();
1811 Start the animation for changing slides
1813 void start_animation(void)
1815 step = (target < center_slide.slide_index) ? -1 : 1;
1816 pf_state = pf_scrolling;
1820 Go to the previous slide
1822 void show_previous_slide(void)
1824 if (step == 0) {
1825 if (center_index > 0) {
1826 target = center_index - 1;
1827 start_animation();
1829 } else if ( step > 0 ) {
1830 target = center_index;
1831 start_animation();
1832 } else {
1833 target = fmax(0, center_index - 2);
1839 Go to the next slide
1841 void show_next_slide(void)
1843 if (step == 0) {
1844 if (center_index < number_of_slides - 1) {
1845 target = center_index + 1;
1846 start_animation();
1848 } else if ( step < 0 ) {
1849 target = center_index;
1850 start_animation();
1851 } else {
1852 target = fmin(center_index + 2, number_of_slides - 1);
1858 Render the slides. Updates only the offscreen buffer.
1860 void render_all_slides(void)
1862 MYLCD(set_background)(G_BRIGHT(0));
1863 /* TODO: Optimizes this by e.g. invalidating rects */
1864 MYLCD(clear_display)();
1866 int nleft = num_slides;
1867 int nright = num_slides;
1869 int index;
1870 if (step == 0) {
1871 /* no animation, boring plain rendering */
1872 for (index = nleft - 2; index >= 0; index--) {
1873 int alpha = (index < nleft - 2) ? 256 : 128;
1874 alpha -= extra_fade;
1875 if (alpha > 0 )
1876 render_slide(&left_slides[index], alpha);
1878 for (index = nright - 2; index >= 0; index--) {
1879 int alpha = (index < nright - 2) ? 256 : 128;
1880 alpha -= extra_fade;
1881 if (alpha > 0 )
1882 render_slide(&right_slides[index], alpha);
1884 } else {
1885 /* the first and last slide must fade in/fade out */
1886 for (index = nleft - 1; index >= 0; index--) {
1887 int alpha = 256;
1888 if (index == nleft - 1)
1889 alpha = (step > 0) ? 0 : 128 - fade / 2;
1890 if (index == nleft - 2)
1891 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1892 if (index == nleft - 3)
1893 alpha = (step > 0) ? 256 - fade / 2 : 256;
1894 render_slide(&left_slides[index], alpha);
1896 for (index = nright - 1; index >= 0; index--) {
1897 int alpha = (index < nright - 2) ? 256 : 128;
1898 if (index == nright - 1)
1899 alpha = (step > 0) ? fade / 2 : 0;
1900 if (index == nright - 2)
1901 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1902 if (index == nright - 3)
1903 alpha = (step > 0) ? 256 : 128 + fade / 2;
1904 render_slide(&right_slides[index], alpha);
1907 render_slide(&center_slide, 256);
1912 Updates the animation effect. Call this periodically from a timer.
1914 void update_scroll_animation(void)
1916 if (step == 0)
1917 return;
1919 int speed = 16384;
1920 int i;
1922 /* deaccelerate when approaching the target */
1923 if (true) {
1924 const int max = 2 * 65536;
1926 int fi = slide_frame;
1927 fi -= (target << 16);
1928 if (fi < 0)
1929 fi = -fi;
1930 fi = fmin(fi, max);
1932 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1933 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1936 slide_frame += speed * step;
1938 int index = slide_frame >> 16;
1939 int pos = slide_frame & 0xffff;
1940 int neg = 65536 - pos;
1941 int tick = (step < 0) ? neg : pos;
1942 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1944 /* the leftmost and rightmost slide must fade away */
1945 fade = pos / 256;
1947 if (step < 0)
1948 index++;
1949 if (center_index != index) {
1950 center_index = index;
1951 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1952 slide_frame = index << 16;
1953 center_slide.slide_index = center_index;
1954 for (i = 0; i < num_slides; i++)
1955 left_slides[i].slide_index = center_index - 1 - i;
1956 for (i = 0; i < num_slides; i++)
1957 right_slides[i].slide_index = center_index + 1 + i;
1960 center_slide.angle = (step * tick * itilt) >> 16;
1961 center_slide.cx = -step * fmul(offsetX, ftick);
1962 center_slide.cy = fmul(offsetY, ftick);
1964 if (center_index == target) {
1965 reset_slides();
1966 pf_state = pf_idle;
1967 step = 0;
1968 fade = 256;
1969 return;
1972 for (i = 0; i < num_slides; i++) {
1973 struct slide_data *si = &left_slides[i];
1974 si->angle = itilt;
1975 si->cx =
1976 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1977 * slide_spacing * ftick);
1978 si->cy = offsetY;
1981 for (i = 0; i < num_slides; i++) {
1982 struct slide_data *si = &right_slides[i];
1983 si->angle = -itilt;
1984 si->cx =
1985 offsetX + slide_spacing * i * PFREAL_ONE - step
1986 * slide_spacing * ftick;
1987 si->cy = offsetY;
1990 if (step > 0) {
1991 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1992 right_slides[0].angle = -(neg * itilt) >> 16;
1993 right_slides[0].cx = fmul(offsetX, ftick);
1994 right_slides[0].cy = fmul(offsetY, ftick);
1995 } else {
1996 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1997 left_slides[0].angle = (pos * itilt) >> 16;
1998 left_slides[0].cx = -fmul(offsetX, ftick);
1999 left_slides[0].cy = fmul(offsetY, ftick);
2002 /* must change direction ? */
2003 if (target < index)
2004 if (step > 0)
2005 step = -1;
2006 if (target > index)
2007 if (step < 0)
2008 step = 1;
2013 Cleanup the plugin
2015 void cleanup(void *parameter)
2017 (void) parameter;
2018 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2019 rb->cpu_boost(false);
2020 #endif
2021 end_pf_thread();
2022 /* Turn on backlight timeout (revert to settings) */
2023 backlight_use_settings(); /* backlight control in lib/helper.c */
2025 #ifdef USEGSLIB
2026 grey_release();
2027 #endif
2031 Create the "?" slide, that is shown while loading
2032 or when no cover was found.
2034 int create_empty_slide(bool force)
2036 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2037 struct bitmap input_bmp;
2038 int ret;
2039 input_bmp.width = DISPLAY_WIDTH;
2040 input_bmp.height = DISPLAY_HEIGHT;
2041 #if LCD_DEPTH > 1
2042 input_bmp.format = FORMAT_NATIVE;
2043 #endif
2044 input_bmp.data = (char*)buf;
2045 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2046 buf_size,
2047 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2048 &format_transposed);
2049 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2050 return false;
2053 return true;
2057 Shows the album name setting menu
2059 int album_name_menu(void)
2061 int selection = show_album_name;
2063 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2064 "Hide album title", "Show at the bottom", "Show at the top");
2065 rb->do_menu(&album_name_menu, &selection, NULL, false);
2067 show_album_name = selection;
2068 return GO_TO_PREVIOUS;
2072 Shows the settings menu
2074 int settings_menu(void)
2076 int selection = 0;
2077 bool old_val;
2079 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2080 "Spacing", "Centre margin", "Number of slides", "Zoom",
2081 "Show album title", "Resize Covers", "Rebuild cache");
2083 do {
2084 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2085 switch(selection) {
2086 case 0:
2087 rb->set_bool("Show FPS", &show_fps);
2088 reset_track_list();
2089 break;
2091 case 1:
2092 rb->set_int("Spacing between slides", "", 1,
2093 &slide_spacing,
2094 NULL, 1, 0, 100, NULL );
2095 recalc_offsets();
2096 reset_slides();
2097 break;
2099 case 2:
2100 rb->set_int("Centre margin", "", 1,
2101 &center_margin,
2102 NULL, 1, 0, 80, NULL );
2103 recalc_offsets();
2104 reset_slides();
2105 break;
2107 case 3:
2108 rb->set_int("Number of slides", "", 1, &num_slides,
2109 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2110 recalc_offsets();
2111 reset_slides();
2112 break;
2114 case 4:
2115 rb->set_int("Zoom", "", 1, &zoom,
2116 NULL, 1, 10, 300, NULL );
2117 recalc_offsets();
2118 reset_slides();
2119 break;
2120 case 5:
2121 album_name_menu();
2122 reset_track_list();
2123 recalc_offsets();
2124 reset_slides();
2125 break;
2126 case 6:
2127 old_val = resize;
2128 rb->set_bool("Resize Covers", &resize);
2129 if (old_val == resize) /* changed? */
2130 break;
2131 /* fallthrough if changed, since cache needs to be rebuilt */
2132 case 7:
2133 cache_version = 0;
2134 rb->remove(EMPTY_SLIDE);
2135 rb->splash(HZ, "Cache will be rebuilt on next restart");
2136 break;
2138 case MENU_ATTACHED_USB:
2139 return PLUGIN_USB_CONNECTED;
2141 } while ( selection >= 0 );
2142 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2143 return 0;
2147 Show the main menu
2149 enum {
2150 PF_GOTO_WPS,
2151 #if PF_PLAYBACK_CAPABLE
2152 PF_MENU_PLAYBACK_CONTROL,
2153 #endif
2154 PF_MENU_SETTINGS,
2155 PF_MENU_RETURN,
2156 PF_MENU_QUIT,
2159 int main_menu(void)
2161 int selection = 0;
2162 int result;
2164 #if LCD_DEPTH > 1
2165 rb->lcd_set_foreground(N_BRIGHT(255));
2166 #endif
2168 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2169 "Go to WPS",
2170 #if PF_PLAYBACK_CAPABLE
2171 "Playback Control",
2172 #endif
2173 "Settings", "Return", "Quit");
2174 while (1) {
2175 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2176 case PF_GOTO_WPS: /* WPS */
2177 return -2;
2178 #if PF_PLAYBACK_CAPABLE
2179 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2180 playback_control(NULL);
2181 break;
2182 #endif
2183 case PF_MENU_SETTINGS:
2184 result = settings_menu();
2185 if ( result != 0 ) return result;
2186 break;
2187 case PF_MENU_RETURN:
2188 return 0;
2189 case PF_MENU_QUIT:
2190 return -1;
2192 case MENU_ATTACHED_USB:
2193 return PLUGIN_USB_CONNECTED;
2195 default:
2196 return 0;
2202 Animation step for zooming into the current cover
2204 void update_cover_in_animation(void)
2206 cover_animation_keyframe++;
2207 if( cover_animation_keyframe < 20 ) {
2208 center_slide.distance-=5;
2209 center_slide.angle+=1;
2210 extra_fade += 13;
2212 else if( cover_animation_keyframe < 35 ) {
2213 center_slide.angle+=16;
2215 else {
2216 cover_animation_keyframe = 0;
2217 pf_state = pf_show_tracks;
2222 Animation step for zooming out the current cover
2224 void update_cover_out_animation(void)
2226 cover_animation_keyframe++;
2227 if( cover_animation_keyframe <= 15 ) {
2228 center_slide.angle-=16;
2230 else if( cover_animation_keyframe < 35 ) {
2231 center_slide.distance+=5;
2232 center_slide.angle-=1;
2233 extra_fade -= 13;
2235 else {
2236 cover_animation_keyframe = 0;
2237 pf_state = pf_idle;
2242 Draw a blue gradient at y with height h
2244 static inline void draw_gradient(int y, int h)
2246 static int r, inc, c;
2247 inc = (100 << 8) / h;
2248 c = 0;
2249 selected_track_pulse = (selected_track_pulse+1) % 10;
2250 int c2 = selected_track_pulse - 5;
2251 for (r=0; r<h; r++) {
2252 #ifdef HAVE_LCD_COLOR
2253 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2254 c2+250-(c >> 8)));
2255 #else
2256 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2257 #endif
2258 MYLCD(hline)(0, LCD_WIDTH, r+y);
2259 if ( r > h/2 )
2260 c-=inc;
2261 else
2262 c+=inc;
2267 static void track_list_yh(int char_height)
2269 switch (show_album_name)
2271 case album_name_hide:
2272 track_list_y = (show_fps ? char_height : 0);
2273 track_list_h = LCD_HEIGHT - track_list_y;
2274 break;
2275 case album_name_bottom:
2276 track_list_y = (show_fps ? char_height : 0);
2277 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2278 break;
2279 default: /* case album_name_top */
2280 track_list_y = char_height * 2;
2281 track_list_h = LCD_HEIGHT - track_list_y -
2282 (show_fps ? char_height : 0);
2283 break;
2288 Reset the track list after a album change
2290 void reset_track_list(void)
2292 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2293 track_list_yh(albumtxt_h);
2294 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2295 start_index_track_list = 0;
2296 track_scroll_index = 0;
2297 track_scroll_dir = 1;
2298 selected_track = 0;
2300 /* let the tracklist start more centered
2301 * if the screen isn't filled with tracks */
2302 if (track_count*albumtxt_h < track_list_h)
2304 track_list_h = track_count * albumtxt_h;
2305 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2310 Display the list of tracks
2312 void show_track_list(void)
2314 MYLCD(clear_display)();
2315 if ( center_slide.slide_index != track_index ) {
2316 create_track_index(center_slide.slide_index);
2317 reset_track_list();
2319 static int titletxt_w, titletxt_x, color, titletxt_h;
2320 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2322 int titletxt_y = track_list_y;
2323 int track_i;
2324 track_i = start_index_track_list;
2325 for (;track_i < track_list_visible_entries+start_index_track_list;
2326 track_i++)
2328 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2329 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2330 if ( track_i == selected_track ) {
2331 draw_gradient(titletxt_y, titletxt_h);
2332 MYLCD(set_foreground)(G_BRIGHT(255));
2333 if (titletxt_w > LCD_WIDTH ) {
2334 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2335 track_scroll_dir = 1;
2336 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2337 track_scroll_index += track_scroll_dir*2;
2338 titletxt_x = track_scroll_index;
2340 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2342 else {
2343 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2344 MYLCD(set_foreground)(G_BRIGHT(color));
2345 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2347 titletxt_y += titletxt_h;
2351 void select_next_track(void)
2353 if ( selected_track < track_count - 1 ) {
2354 selected_track++;
2355 track_scroll_index = 0;
2356 track_scroll_dir = 1;
2357 if (selected_track==(track_list_visible_entries+start_index_track_list))
2358 start_index_track_list++;
2362 void select_prev_track(void)
2364 if (selected_track > 0 ) {
2365 if (selected_track==start_index_track_list) start_index_track_list--;
2366 track_scroll_index = 0;
2367 track_scroll_dir = 1;
2368 selected_track--;
2372 #if PF_PLAYBACK_CAPABLE
2374 * Puts the current tracklist into a newly created playlist and starts playling
2376 void start_playback(void)
2378 static int old_playlist = -1, old_shuffle = 0;
2379 int count = 0;
2380 int position = selected_track;
2381 int shuffle = rb->global_settings->playlist_shuffle;
2382 /* reuse existing playlist if possible
2383 * regenerate if shuffle is on or changed, since playlist index and
2384 * selected track are "out of sync" */
2385 if (!shuffle && center_slide.slide_index == old_playlist
2386 && (old_shuffle == shuffle))
2388 goto play;
2390 /* First, replace the current playlist with a new one */
2391 else if (rb->playlist_remove_all_tracks(NULL) == 0
2392 && rb->playlist_create(NULL, NULL) == 0)
2394 do {
2395 rb->yield();
2396 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2397 PLAYLIST_INSERT_LAST, false, true) < 0)
2398 break;
2399 } while(++count < track_count);
2400 rb->playlist_sync(NULL);
2402 else
2403 return;
2405 if (rb->global_settings->playlist_shuffle)
2406 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2407 play:
2408 /* TODO: can we adjust selected_track if !play_selected ?
2409 * if shuffle, we can't predict the playing track easily, and for either
2410 * case the track list doesn't get auto scrolled*/
2411 rb->playlist_start(position, 0);
2412 old_playlist = center_slide.slide_index;
2413 old_shuffle = shuffle;
2415 #endif
2418 Draw the current album name
2420 void draw_album_text(void)
2422 if (0 == show_album_name)
2423 return;
2425 int albumtxt_w, albumtxt_h;
2426 int albumtxt_y = 0;
2428 char *albumtxt;
2429 int c;
2430 /* Draw album text */
2431 if ( pf_state == pf_scrolling ) {
2432 c = ((slide_frame & 0xffff )/ 255);
2433 if (step < 0) c = 255-c;
2434 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2435 albumtxt = get_album_name(center_index+step);
2436 c = (c-128)*2;
2438 else {
2439 albumtxt = get_album_name(center_index);
2440 c = (128-c)*2;
2443 else {
2444 c= 255;
2445 albumtxt = get_album_name(center_index);
2448 MYLCD(set_foreground)(G_BRIGHT(c));
2449 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2450 if (center_index != prev_center_index) {
2451 albumtxt_x = 0;
2452 albumtxt_dir = -1;
2453 prev_center_index = center_index;
2456 if (show_album_name == album_name_top)
2457 albumtxt_y = albumtxt_h / 2;
2458 else
2459 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2461 if (albumtxt_w > LCD_WIDTH ) {
2462 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2463 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2464 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2465 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2466 albumtxt_x += albumtxt_dir;
2469 else {
2470 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2477 Display an error message and wait for input.
2479 void error_wait(const char *message)
2481 rb->splashf(0, "%s. Press any button to continue.", message);
2482 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2483 rb->yield();
2484 rb->sleep(2 * HZ);
2488 Main function that also contain the main plasma
2489 algorithm.
2491 int main(void)
2493 int ret;
2495 rb->lcd_setfont(FONT_UI);
2496 draw_splashscreen();
2498 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2499 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2500 error_wait("Could not create directory " CACHE_PREFIX);
2501 return PLUGIN_ERROR;
2505 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2507 init_reflect_table();
2509 ALIGN_BUFFER(buf, buf_size, 4);
2510 ret = create_album_index();
2511 if (ret == ERROR_BUFFER_FULL) {
2512 error_wait("Not enough memory for album names");
2513 return PLUGIN_ERROR;
2514 } else if (ret == ERROR_NO_ALBUMS) {
2515 error_wait("No albums found. Please enable database");
2516 return PLUGIN_ERROR;
2519 ALIGN_BUFFER(buf, buf_size, 4);
2520 number_of_slides = album_count;
2521 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2522 error_wait("Could not create album art cache");
2523 return PLUGIN_ERROR;
2526 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2527 error_wait("Could not load the empty slide");
2528 return PLUGIN_ERROR;
2530 cache_version = CACHE_VERSION;
2531 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2534 #ifdef USEGSLIB
2535 long grey_buf_used;
2536 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2537 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2539 error_wait("Greylib init failed!");
2540 return PLUGIN_ERROR;
2542 grey_setfont(FONT_UI);
2543 buf_size -= grey_buf_used;
2544 buf = (void*)(grey_buf_used + (char*)buf);
2545 #endif
2546 buflib_init(&buf_ctx, (void *)buf, buf_size);
2548 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2550 error_wait("Unable to load empty slide image");
2551 return PLUGIN_ERROR;
2554 if (!create_pf_thread()) {
2555 error_wait("Cannot create thread!");
2556 return PLUGIN_ERROR;
2559 int i;
2561 /* initialize */
2562 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2563 cache[i].hid = 0;
2564 cache[i].index = 0;
2565 cache[i].next = i + 1;
2566 cache[i].prev = i - 1;
2568 cache[0].prev = i - 1;
2569 cache[i - 1].next = 0;
2570 cache_free = 0;
2571 buffer = LCD_BUF;
2573 pf_state = pf_idle;
2575 track_index = -1;
2576 extra_fade = 0;
2577 slide_frame = 0;
2578 step = 0;
2579 target = 0;
2580 fade = 256;
2582 recalc_offsets();
2583 reset_slides();
2585 char fpstxt[10];
2586 int button;
2588 int frames = 0;
2589 long last_update = *rb->current_tick;
2590 long current_update;
2591 long update_interval = 100;
2592 int fps = 0;
2593 int fpstxt_y;
2595 bool instant_update;
2596 #ifdef USEGSLIB
2597 grey_show(true);
2598 grey_set_drawmode(DRMODE_FG);
2599 #endif
2600 rb->lcd_set_drawmode(DRMODE_FG);
2601 while (true) {
2602 current_update = *rb->current_tick;
2603 frames++;
2605 /* Initial rendering */
2606 instant_update = false;
2608 /* Handle states */
2609 switch ( pf_state ) {
2610 case pf_scrolling:
2611 update_scroll_animation();
2612 render_all_slides();
2613 instant_update = true;
2614 break;
2615 case pf_cover_in:
2616 update_cover_in_animation();
2617 render_all_slides();
2618 instant_update = true;
2619 break;
2620 case pf_cover_out:
2621 update_cover_out_animation();
2622 render_all_slides();
2623 instant_update = true;
2624 break;
2625 case pf_show_tracks:
2626 show_track_list();
2627 break;
2628 case pf_idle:
2629 render_all_slides();
2630 break;
2633 /* Calculate FPS */
2634 if (current_update - last_update > update_interval) {
2635 fps = frames * HZ / (current_update - last_update);
2636 last_update = current_update;
2637 frames = 0;
2639 /* Draw FPS */
2640 if (show_fps)
2642 #ifdef USEGSLIB
2643 MYLCD(set_foreground)(G_BRIGHT(255));
2644 #else
2645 MYLCD(set_foreground)(G_PIX(255,0,0));
2646 #endif
2647 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2648 if (show_album_name == album_name_top)
2649 fpstxt_y = LCD_HEIGHT -
2650 rb->screens[SCREEN_MAIN]->getcharheight();
2651 else
2652 fpstxt_y = 0;
2653 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2655 draw_album_text();
2658 /* Copy offscreen buffer to LCD and give time to other threads */
2659 MYLCD(update)();
2660 rb->yield();
2662 /*/ Handle buttons */
2663 button = rb->get_custom_action(CONTEXT_PLUGIN
2664 #ifndef USE_CORE_PREVNEXT
2665 |(pf_state == pf_show_tracks ? 1 : 0)
2666 #endif
2667 ,instant_update ? 0 : HZ/16,
2668 get_context_map);
2670 switch (button) {
2671 case PF_QUIT:
2672 return PLUGIN_OK;
2673 case PF_WPS:
2674 return PLUGIN_GOTO_WPS;
2675 case PF_BACK:
2676 if ( pf_state == pf_show_tracks )
2678 buflib_buffer_in(&buf_ctx, borrowed);
2679 borrowed = 0;
2680 track_index = -1;
2681 pf_state = pf_cover_out;
2683 if (pf_state == pf_idle || pf_state == pf_scrolling)
2684 return PLUGIN_OK;
2685 break;
2686 case PF_MENU:
2687 #ifdef USEGSLIB
2688 grey_show(false);
2689 #endif
2690 ret = main_menu();
2691 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2692 if ( ret == -1 ) return PLUGIN_OK;
2693 if ( ret != 0 ) return ret;
2694 #ifdef USEGSLIB
2695 grey_show(true);
2696 #endif
2697 MYLCD(set_drawmode)(DRMODE_FG);
2698 break;
2700 case PF_NEXT:
2701 case PF_NEXT_REPEAT:
2702 if ( pf_state == pf_show_tracks )
2703 select_next_track();
2704 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2705 show_next_slide();
2706 break;
2708 case PF_PREV:
2709 case PF_PREV_REPEAT:
2710 if ( pf_state == pf_show_tracks )
2711 select_prev_track();
2712 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2713 show_previous_slide();
2714 break;
2716 case PF_SELECT:
2717 if ( pf_state == pf_idle ) {
2718 pf_state = pf_cover_in;
2720 else if ( pf_state == pf_show_tracks ) {
2721 #if PF_PLAYBACK_CAPABLE
2722 start_playback();
2723 #endif
2725 break;
2726 default:
2727 if (rb->default_event_handler_ex(button, cleanup, NULL)
2728 == SYS_USB_CONNECTED)
2729 return PLUGIN_USB_CONNECTED;
2730 break;
2737 /*************************** Plugin entry point ****************************/
2739 enum plugin_status plugin_start(const void *parameter)
2741 int ret;
2742 (void) parameter;
2743 #if LCD_DEPTH > 1
2744 rb->lcd_set_backdrop(NULL);
2745 #endif
2746 /* Turn off backlight timeout */
2747 backlight_force_on(); /* backlight control in lib/helper.c */
2748 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2749 rb->cpu_boost(true);
2750 #endif
2751 #if PF_PLAYBACK_CAPABLE
2752 buf = rb->plugin_get_buffer(&buf_size);
2753 #else
2754 buf = rb->plugin_get_audio_buffer(&buf_size);
2755 #ifndef SIMULATOR
2756 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2758 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2759 buf_size = MIN(buf_size, tmp_size);
2761 #endif
2762 #endif
2763 ret = main();
2764 if ( ret == PLUGIN_OK ) {
2765 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2766 CONFIG_VERSION))
2768 rb->splash(HZ, "Error writing config.");
2769 ret = PLUGIN_ERROR;
2773 cleanup(NULL);
2774 return ret;