Remove use of uniqbuf in PictureFlow, preventing crash on 64-bit sim and freeing...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobcd12152d430a6d3ee5526cfcd8faacd0f3ca83f4
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 ***********************************/
40 #define PF_PREV ACTION_STD_PREV
41 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
42 #define PF_NEXT ACTION_STD_NEXT
43 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
44 #define PF_SELECT ACTION_STD_OK
45 #define PF_CONTEXT ACTION_STD_CONTEXT
46 #define PF_BACK ACTION_STD_CANCEL
47 #define PF_MENU ACTION_STD_MENU
48 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
50 const struct button_mapping pf_context_album_scroll[] =
52 #ifdef HAVE_TOUCHSCREEN
53 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
54 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
55 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
56 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
57 #endif
58 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
59 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
60 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
61 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
62 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
63 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
64 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
65 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
66 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
67 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
68 CONFIG_KEYPAD == MEIZU_M6SL_PAD
69 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
70 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
71 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
72 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
73 #elif CONFIG_KEYPAD == ONDIO_PAD
74 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
75 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
76 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
77 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
79 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
80 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
81 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
82 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
83 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
84 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
85 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
86 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
87 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
88 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
89 #endif
90 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
93 const struct button_mapping pf_context_buttons[] =
95 #ifdef HAVE_TOUCHSCREEN
96 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
97 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
98 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
99 #endif
100 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
101 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
102 #elif CONFIG_KEYPAD == SANSA_C100_PAD
103 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
104 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
105 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
106 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
107 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
108 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
109 CONFIG_KEYPAD == SANSA_FUZE_PAD
110 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
111 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
113 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
114 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
115 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
116 #if CONFIG_KEYPAD == COWOND2_PAD
117 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
118 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
119 #endif
120 #elif CONFIG_KEYPAD == SANSA_E200_PAD
121 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
122 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
123 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
124 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
125 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
126 || (CONFIG_KEYPAD == IPOD_4G_PAD)
127 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
128 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
129 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
130 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
131 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
132 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
133 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
134 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
135 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
136 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
137 #endif
138 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
139 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
140 #else
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
142 #endif
144 const struct button_mapping *pf_contexts[] =
146 pf_context_album_scroll,
147 pf_context_buttons
150 #if LCD_DEPTH < 8
151 #if LCD_DEPTH > 1
152 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
153 #else /* LCD_DEPTH <= 1 */
154 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
155 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
156 #define PICTUREFLOW_DRMODE DRMODE_SOLID
157 #else
158 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
159 #endif
160 #endif /* LCD_DEPTH <= 1 */
161 #define USEGSLIB
162 GREY_INFO_STRUCT
163 #define LCD_BUF _grey_info.buffer
164 #define MYLCD(fn) grey_ ## fn
165 #define G_PIX(r,g,b) \
166 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
167 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
168 #define G_BRIGHT(y) (y)
169 #define BUFFER_WIDTH _grey_info.width
170 #define BUFFER_HEIGHT _grey_info.height
171 typedef unsigned char pix_t;
172 #else /* LCD_DEPTH >= 8 */
173 #define LCD_BUF rb->lcd_framebuffer
174 #define MYLCD(fn) rb->lcd_ ## fn
175 #define G_PIX LCD_RGBPACK
176 #define N_PIX LCD_RGBPACK
177 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
178 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
179 #define BUFFER_WIDTH LCD_WIDTH
180 #define BUFFER_HEIGHT LCD_HEIGHT
181 typedef fb_data pix_t;
182 #endif /* LCD_DEPTH >= 8 */
184 /* for fixed-point arithmetic, we need minimum 32-bit long
185 long long (64-bit) might be useful for multiplication and division */
186 #define PFreal long
187 #define PFREAL_SHIFT 10
188 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
189 #define PFREAL_ONE (1 << PFREAL_SHIFT)
190 #define PFREAL_HALF (PFREAL_ONE >> 1)
193 #define IANGLE_MAX 1024
194 #define IANGLE_MASK 1023
196 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
197 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
198 #define DISPLAY_HEIGHT REFLECT_TOP
199 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
200 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
201 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
202 (REFLECT_HEIGHT * 5))
203 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
204 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
205 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
206 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
207 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
209 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
211 #define MAX_SLIDES_COUNT 10
213 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
214 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
216 #define EV_EXIT 9999
217 #define EV_WAKEUP 1337
219 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
220 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
221 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
223 /* Error return values */
224 #define ERROR_NO_ALBUMS -1
225 #define ERROR_BUFFER_FULL -2
227 /* current version for cover cache */
228 #define CACHE_VERSION 3
229 #define CONFIG_VERSION 1
230 #define CONFIG_FILE "pictureflow.cfg"
232 /** structs we use */
234 struct slide_data {
235 int slide_index;
236 int angle;
237 PFreal cx;
238 PFreal cy;
239 PFreal distance;
242 struct slide_cache {
243 int index; /* index of the cached slide */
244 int hid; /* handle ID of the cached slide */
245 short next; /* "next" slide, with LRU last */
246 short prev; /* "previous" slide */
249 struct album_data {
250 int name_idx;
251 long seek;
254 struct track_data {
255 uint32_t sort;
256 int name_idx;
257 long seek;
260 struct rect {
261 int left;
262 int right;
263 int top;
264 int bottom;
267 struct load_slide_event_data {
268 int slide_index;
269 int cache_index;
273 struct pfraw_header {
274 int32_t width; /* bmap width in pixels */
275 int32_t height; /* bmap height in pixels */
278 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
279 album_name_top };
280 static char* show_album_name_conf[] =
282 "hide",
283 "bottom",
284 "top"
287 #define MAX_SPACING 40
288 #define MAX_MARGIN 80
290 /* config values and their defaults */
291 static int slide_spacing = DISPLAY_WIDTH / 4;
292 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
293 static int num_slides = 4;
294 static int zoom = 100;
295 static bool show_fps = false;
296 static bool resize = true;
297 static int cache_version = 0;
298 static int show_album_name = (LCD_HEIGHT > 100)
299 ? album_name_top : album_name_bottom;
301 static struct configdata config[] =
303 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
304 NULL },
305 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
306 NULL },
307 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
308 NULL },
309 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
310 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
311 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
312 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
313 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
314 show_album_name_conf }
317 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
319 /** below we allocate the memory we want to use **/
321 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
322 static uint8_t reflect_table[REFLECT_HEIGHT];
323 static struct slide_data center_slide;
324 static struct slide_data left_slides[MAX_SLIDES_COUNT];
325 static struct slide_data right_slides[MAX_SLIDES_COUNT];
326 static int slide_frame;
327 static int step;
328 static int target;
329 static int fade;
330 static int center_index = 0; /* index of the slide that is in the center */
331 static int itilt;
332 static PFreal offsetX;
333 static PFreal offsetY;
334 static int number_of_slides;
336 static struct slide_cache cache[SLIDE_CACHE_SIZE];
337 static int cache_free;
338 static int cache_used = -1;
339 static int cache_left_index = -1;
340 static int cache_right_index = -1;
341 static int cache_center_index = -1;
343 /* use long for aligning */
344 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
345 /* queue (as array) for scheduling load_surface */
347 static int empty_slide_hid;
349 unsigned int thread_id;
350 struct event_queue thread_q;
352 static struct tagcache_search tcs;
354 static struct buflib_context buf_ctx;
356 static struct album_data *album;
357 static char *album_names;
358 static int album_count;
360 static struct track_data *tracks;
361 static char *track_names;
362 static size_t borrowed = 0;
363 static int track_count;
364 static int track_index;
365 static int selected_track;
366 static int selected_track_pulse;
367 void reset_track_list(void);
369 void * buf;
370 size_t buf_size;
372 static bool thread_is_running;
374 static int cover_animation_keyframe;
375 static int extra_fade;
377 static int albumtxt_x = 0;
378 static int albumtxt_dir = -1;
379 static int prev_center_index = -1;
381 static int start_index_track_list = 0;
382 static int track_list_visible_entries = 0;
383 static int track_list_y;
384 static int track_list_h;
385 static int track_scroll_index = 0;
386 static int track_scroll_dir = 1;
389 Proposals for transitions:
391 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
392 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
394 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
396 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
398 TODO:
399 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
400 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
402 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
404 enum pf_states {
405 pf_idle = 0,
406 pf_scrolling,
407 pf_cover_in,
408 pf_show_tracks,
409 pf_cover_out
412 static int pf_state;
414 /** code */
415 static bool free_slide_prio(int prio);
416 static inline unsigned fade_color(pix_t c, unsigned a);
417 bool save_pfraw(char* filename, struct bitmap *bm);
418 bool load_new_slide(void);
419 int load_surface(int);
421 static inline PFreal fmul(PFreal a, PFreal b)
423 return (a*b) >> PFREAL_SHIFT;
427 * This version preshifts each operand, which is useful when we know how many
428 * of the least significant bits will be empty, or are worried about overflow
429 * in a particular calculation
431 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
433 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
436 /* ARMv5+ has a clz instruction equivalent to our function.
438 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
439 static inline int clz(uint32_t v)
441 return __builtin_clz(v);
444 /* Otherwise, use our clz, which can be inlined */
445 #elif defined(CPU_COLDFIRE)
446 /* This clz is based on the log2(n) implementation at
447 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
448 * A clz benchmark plugin showed this to be about 14% faster on coldfire
449 * than the LUT-based version.
451 static inline int clz(uint32_t v)
453 int r = 32;
454 if (v >= 0x10000)
456 v >>= 16;
457 r -= 16;
459 if (v & 0xff00)
461 v >>= 8;
462 r -= 8;
464 if (v & 0xf0)
466 v >>= 4;
467 r -= 4;
469 if (v & 0xc)
471 v >>= 2;
472 r -= 2;
474 if (v & 2)
476 v >>= 1;
477 r -= 1;
479 r -= v;
480 return r;
482 #else
483 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
484 0, 0, 0, 0, 0, 0, 0, 0 };
485 /* This clz is based on the log2(n) implementation at
486 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
487 * It is not any faster than the one above, but trades 16B in the lookup table
488 * for a savings of 12B per each inlined call.
490 static inline int clz(uint32_t v)
492 int r = 28;
493 if (v >= 0x10000)
495 v >>= 16;
496 r -= 16;
498 if (v & 0xff00)
500 v >>= 8;
501 r -= 8;
503 if (v & 0xf0)
505 v >>= 4;
506 r -= 4;
508 return r + clz_lut[v];
510 #endif
512 /* Return the maximum possible left shift for a signed int32, without
513 * overflow
515 static inline int allowed_shift(int32_t val)
517 uint32_t uval = val ^ (val >> 31);
518 return clz(uval) - 1;
521 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
522 * num and den before dividing.
524 static inline PFreal fdiv(PFreal num, PFreal den)
526 int shift = allowed_shift(num);
527 shift = MIN(PFREAL_SHIFT, shift);
528 num <<= shift;
529 den >>= PFREAL_SHIFT - shift;
530 return num / den;
533 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
534 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
535 #define fabs(a) (a < 0 ? -a : a)
536 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
538 #if CONFIG_CPU == SH7034
539 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
540 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
541 #else
542 #define MULUQ(a, b) ((a) * (b))
543 #endif
546 #if 0
547 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
548 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
550 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
551 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
553 static inline PFreal fmul(PFreal a, PFreal b)
555 return (a*b) >> PFREAL_SHIFT;
558 static inline PFreal fdiv(PFreal n, PFreal m)
560 return (n<<(PFREAL_SHIFT))/m;
562 #endif
564 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
565 static const short sin_tab[] = {
566 0, 100, 200, 297, 392, 483, 569, 650,
567 724, 792, 851, 903, 946, 980, 1004, 1019,
568 1024, 1019, 1004, 980, 946, 903, 851, 792,
569 724, 650, 569, 483, 392, 297, 200, 100,
570 0, -100, -200, -297, -392, -483, -569, -650,
571 -724, -792, -851, -903, -946, -980, -1004, -1019,
572 -1024, -1019, -1004, -980, -946, -903, -851, -792,
573 -724, -650, -569, -483, -392, -297, -200, -100,
577 static inline PFreal fsin(int iangle)
579 iangle &= IANGLE_MASK;
581 int i = (iangle >> 4);
582 PFreal p = sin_tab[i];
583 PFreal q = sin_tab[(i+1)];
584 PFreal g = (q - p);
585 return p + g * (iangle-i*16)/16;
588 static inline PFreal fcos(int iangle)
590 return fsin(iangle + (IANGLE_MAX >> 2));
593 static inline unsigned scale_val(unsigned val, unsigned bits)
595 val = val * ((1 << bits) - 1);
596 return ((val >> 8) + val + 128) >> 8;
599 static void output_row_8_transposed(uint32_t row, void * row_in,
600 struct scaler_context *ctx)
602 pix_t *dest = (pix_t*)ctx->bm->data + row;
603 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
604 #ifdef USEGSLIB
605 uint8_t *qp = (uint8_t*)row_in;
606 for (; dest < end; dest += ctx->bm->height)
607 *dest = *qp++;
608 #else
609 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
610 unsigned r, g, b;
611 for (; dest < end; dest += ctx->bm->height)
613 r = scale_val(qp->red, 5);
614 g = scale_val(qp->green, 6);
615 b = scale_val((qp++)->blue, 5);
616 *dest = LCD_RGBPACK_LCD(r,g,b);
618 #endif
621 static void output_row_32_transposed(uint32_t row, void * row_in,
622 struct scaler_context *ctx)
624 pix_t *dest = (pix_t*)ctx->bm->data + row;
625 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
626 #ifdef USEGSLIB
627 uint32_t *qp = (uint32_t*)row_in;
628 for (; dest < end; dest += ctx->bm->height)
629 *dest = SC_OUT(*qp++, ctx);
630 #else
631 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
632 int r, g, b;
633 for (; dest < end; dest += ctx->bm->height)
635 r = scale_val(SC_OUT(qp->r, ctx), 5);
636 g = scale_val(SC_OUT(qp->g, ctx), 6);
637 b = scale_val(SC_OUT(qp->b, ctx), 5);
638 qp++;
639 *dest = LCD_RGBPACK_LCD(r,g,b);
641 #endif
644 #ifdef HAVE_LCD_COLOR
645 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
646 struct scaler_context *ctx)
648 pix_t *dest = (pix_t*)ctx->bm->data + row;
649 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
650 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
651 for (; dest < end; dest += ctx->bm->height)
653 unsigned r, g, b, y, u, v;
654 y = SC_OUT(qp->b, ctx);
655 u = SC_OUT(qp->g, ctx);
656 v = SC_OUT(qp->r, ctx);
657 qp++;
658 yuv_to_rgb(y, u, v, &r, &g, &b);
659 r = scale_val(r, 5);
660 g = scale_val(g, 6);
661 b = scale_val(b, 5);
662 *dest = LCD_RGBPACK_LCD(r, g, b);
665 #endif
667 static unsigned int get_size(struct bitmap *bm)
669 return bm->width * bm->height * sizeof(pix_t);
672 const struct custom_format format_transposed = {
673 .output_row_8 = output_row_8_transposed,
674 #ifdef HAVE_LCD_COLOR
675 .output_row_32 = {
676 output_row_32_transposed,
677 output_row_32_transposed_fromyuv
679 #else
680 .output_row_32 = output_row_32_transposed,
681 #endif
682 .get_size = get_size
685 static const struct button_mapping* get_context_map(int context)
687 return pf_contexts[context & ~CONTEXT_CUSTOM];
690 /* Create the lookup table with the scaling values for the reflections */
691 void init_reflect_table(void)
693 int i;
694 for (i = 0; i < REFLECT_HEIGHT; i++)
695 reflect_table[i] =
696 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
697 (5 * REFLECT_HEIGHT);
701 Create an index of all albums from the database.
702 Also store the album names so we can access them later.
704 int create_album_index(void)
706 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
707 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
708 album_count = 0;
709 rb->tagcache_search(&tcs, tag_album);
710 unsigned int l, old_l = 0;
711 album_names = buf;
712 album[0].name_idx = 0;
713 while (rb->tagcache_get_next(&tcs))
715 buf_size -= sizeof(struct album_data);
716 l = tcs.result_len;
717 if ( album_count > 0 )
718 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
720 if ( l > buf_size )
721 /* not enough memory */
722 return ERROR_BUFFER_FULL;
724 rb->strcpy(buf, tcs.result);
725 buf_size -= l;
726 buf = l + (char *)buf;
727 DEBUGF("%lX: %s\n", tcs.idxfd[tag_album] ? rb->lseek(tcs.idxfd[tag_album], 0, SEEK_CUR) : -1, tcs.result);
728 album[-album_count].seek = tcs.result_seek;
729 old_l = l;
730 album_count++;
732 rb->tagcache_search_finish(&tcs);
733 ALIGN_BUFFER(buf, buf_size, 4);
734 int i;
735 struct album_data* tmp_album = (struct album_data*)buf;
736 for (i = album_count - 1; i >= 0; i--)
737 tmp_album[i] = album[-i];
738 album = tmp_album;
739 buf = album + album_count;
740 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
744 Return a pointer to the album name of the given slide_index
746 char* get_album_name(const int slide_index)
748 return album_names + album[slide_index].name_idx;
752 Return a pointer to the track name of the active album
753 create_track_index has to be called first.
755 char* get_track_name(const int track_index)
757 if ( track_index < track_count )
758 return track_names + tracks[track_index].name_idx;
759 return 0;
763 Compare two unsigned ints passed via pointers.
765 int compare_tracks (const void *a_v, const void *b_v)
767 uint32_t a = ((struct track_data *)a_v)->sort;
768 uint32_t b = ((struct track_data *)b_v)->sort;
769 return (int)(a - b);
773 Create the track index of the given slide_index.
775 void create_track_index(const int slide_index)
777 if ( slide_index == track_index )
778 return;
779 track_index = slide_index;
781 if (!rb->tagcache_search(&tcs, tag_title))
782 goto fail;
784 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
785 track_count=0;
786 int string_index = 0, track_num;
787 int disc_num;
788 size_t out = 0;
789 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
790 borrowed += out;
791 int avail = borrowed;
792 tracks = (struct track_data*)(track_names + borrowed);
793 while (rb->tagcache_get_next(&tcs))
795 avail -= sizeof(struct track_data);
796 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
797 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
798 int len = 0;
799 if (disc_num < 0)
800 disc_num = 0;
801 retry:
802 if (track_num >= 0)
804 if (disc_num)
805 len = 1 + rb->snprintf(track_names + string_index , avail,
806 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
807 else
808 len = 1 + rb->snprintf(track_names + string_index , avail,
809 "%d: %s", track_num + 1, tcs.result);
811 else
813 track_num = 0;
814 len = tcs.result_len;
815 rb->strncpy(track_names + string_index, tcs.result, avail);
817 if (len > avail)
819 while (len > avail)
821 if (!free_slide_prio(0))
822 goto fail;
823 out = 0;
824 buflib_buffer_out(&buf_ctx, &out);
825 avail += out;
826 borrowed += out;
827 if (track_count)
829 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
830 unsigned int bytes = track_count * sizeof(struct track_data);
831 rb->memmove(new_tracks, tracks, bytes);
832 tracks = new_tracks;
835 goto retry;
838 avail -= len;
839 tracks--;
840 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
841 tracks->name_idx = string_index;
842 tracks->seek = tcs.result_seek;
843 track_count++;
844 string_index += len;
847 rb->tagcache_search_finish(&tcs);
849 /* now fix the track list order */
850 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
851 return;
852 fail:
853 track_count = 0;
854 return;
858 Determine filename of the album art for the given slide_index and
859 store the result in buf.
860 The algorithm looks for the first track of the given album uses
861 find_albumart to find the filename.
863 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
864 int buflen)
866 if ( slide_index == -1 )
868 rb->strncpy( buf, EMPTY_SLIDE, buflen );
871 if (!rb->tagcache_search(&tcs, tag_filename))
872 return false;
874 bool result;
875 /* find the first track of the album */
876 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
878 if ( rb->tagcache_get_next(&tcs) ) {
879 struct mp3entry id3;
880 int fd;
882 fd = rb->open(tcs.result, O_RDONLY);
883 rb->get_metadata(&id3, fd, tcs.result);
884 rb->close(fd);
885 if ( search_albumart_files(&id3, "", buf, buflen) )
886 result = true;
887 else
888 result = false;
890 else {
891 /* did not find a matching track */
892 result = false;
894 rb->tagcache_search_finish(&tcs);
895 return result;
899 Draw the PictureFlow logo
901 void draw_splashscreen(void)
903 unsigned char * buf_tmp = buf;
904 size_t buf_tmp_size = buf_size;
905 struct screen* display = rb->screens[0];
906 #if FB_DATA_SZ > 1
907 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
908 #endif
909 struct bitmap logo = {
910 #if LCD_WIDTH < 200
911 .width = 100,
912 .height = 18,
913 #else
914 .width = 193,
915 .height = 34,
916 #endif
917 .data = buf_tmp
919 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
920 NULL);
921 #if LCD_DEPTH > 1
922 rb->lcd_set_background(N_BRIGHT(0));
923 rb->lcd_set_foreground(N_BRIGHT(255));
924 #else
925 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
926 #endif
927 rb->lcd_clear_display();
929 if (ret > 0)
931 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
932 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
933 #endif
934 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
935 logo.width, logo.height);
936 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
937 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
938 #endif
941 rb->lcd_update();
946 Draw a simple progress bar
948 void draw_progressbar(int step)
950 int txt_w, txt_h;
951 const int bar_height = 22;
952 const int w = LCD_WIDTH - 20;
953 const int x = 10;
955 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
957 int y = (LCD_HEIGHT - txt_h)/2;
959 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
960 y += (txt_h + 5);
962 #if LCD_DEPTH > 1
963 rb->lcd_set_foreground(N_BRIGHT(100));
964 #endif
965 rb->lcd_drawrect(x, y, w+2, bar_height);
966 #if LCD_DEPTH > 1
967 rb->lcd_set_foreground(N_PIX(165, 231, 82));
968 #endif
970 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
971 #if LCD_DEPTH > 1
972 rb->lcd_set_foreground(N_BRIGHT(255));
973 #endif
974 rb->lcd_update();
975 rb->yield();
979 Precomupte the album art images and store them in CACHE_PREFIX.
981 bool create_albumart_cache(void)
983 int ret;
985 int i, slides = 0;
986 struct bitmap input_bmp;
988 char pfraw_file[MAX_PATH];
989 char albumart_file[MAX_PATH];
990 unsigned int format = FORMAT_NATIVE;
991 cache_version = 0;
992 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
993 if (resize)
994 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
995 for (i=0; i < album_count; i++)
997 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
999 /* delete existing cache, so it's a true rebuild */
1000 if(rb->file_exists(pfraw_file))
1001 rb->remove(pfraw_file);
1002 draw_progressbar(i);
1003 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1004 continue;
1006 input_bmp.data = buf;
1007 input_bmp.width = DISPLAY_WIDTH;
1008 input_bmp.height = DISPLAY_HEIGHT;
1009 ret = read_image_file(albumart_file, &input_bmp,
1010 buf_size, format, &format_transposed);
1011 if (ret <= 0) {
1012 rb->splash(HZ, "Could not read bmp");
1013 continue; /* skip missing/broken files */
1015 if (!save_pfraw(pfraw_file, &input_bmp))
1017 rb->splash(HZ, "Could not write bmp");
1019 slides++;
1020 if ( rb->button_get(false) == PF_MENU ) return false;
1022 if ( slides == 0 ) {
1023 /* Warn the user that we couldn't find any albumart */
1024 rb->splash(2*HZ, "No album art found");
1025 return false;
1027 return true;
1031 Thread used for loading and preparing bitmaps in the background
1033 void thread(void)
1035 long sleep_time = 5 * HZ;
1036 struct queue_event ev;
1037 while (1) {
1038 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1039 switch (ev.id) {
1040 case EV_EXIT:
1041 return;
1042 case EV_WAKEUP:
1043 /* we just woke up */
1044 break;
1046 while ( load_new_slide() ) {
1047 rb->yield();
1048 switch (ev.id) {
1049 case EV_EXIT:
1050 return;
1058 End the thread by posting the EV_EXIT event
1060 void end_pf_thread(void)
1062 if ( thread_is_running ) {
1063 rb->queue_post(&thread_q, EV_EXIT, 0);
1064 rb->thread_wait(thread_id);
1065 /* remove the thread's queue from the broadcast list */
1066 rb->queue_delete(&thread_q);
1067 thread_is_running = false;
1074 Create the thread an setup the event queue
1076 bool create_pf_thread(void)
1078 /* put the thread's queue in the bcast list */
1079 rb->queue_init(&thread_q, true);
1080 if ((thread_id = rb->create_thread(
1081 thread,
1082 thread_stack,
1083 sizeof(thread_stack),
1085 "Picture load thread"
1086 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1087 PRIORITY_REALTIME + 1))
1088 IF_COP(, CPU)
1090 ) == 0) {
1091 return false;
1093 thread_is_running = true;
1094 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1095 return true;
1099 Safe the given bitmap as filename in the pfraw format
1101 bool save_pfraw(char* filename, struct bitmap *bm)
1103 struct pfraw_header bmph;
1104 bmph.width = bm->width;
1105 bmph.height = bm->height;
1106 int fh = rb->creat( filename );
1107 if( fh < 0 ) return false;
1108 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1109 int y;
1110 for( y = 0; y < bm->height; y++ )
1112 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1113 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1115 rb->close( fh );
1116 return true;
1121 * The following functions implement the linked-list-in-array used to manage
1122 * the LRU cache of slides, and the list of free cache slots.
1125 #define seek_right_while(start, cond) \
1126 ({ \
1127 int ind_, next_ = (start); \
1128 do { \
1129 ind_ = next_; \
1130 next_ = cache[ind_].next; \
1131 } while (next_ != cache_used && (cond)); \
1132 ind_; \
1135 #define seek_left_while(start, cond) \
1136 ({ \
1137 int ind_, next_ = (start); \
1138 do { \
1139 ind_ = next_; \
1140 next_ = cache[ind_].prev; \
1141 } while (ind_ != cache_used && (cond)); \
1142 ind_; \
1146 Pop the given item from the linked list starting at *head, returning the next
1147 item, or -1 if the list is now empty.
1149 static inline int lla_pop_item (int *head, int i)
1151 int prev = cache[i].prev;
1152 int next = cache[i].next;
1153 if (i == next)
1155 *head = -1;
1156 return -1;
1158 else if (i == *head)
1159 *head = next;
1160 cache[next].prev = prev;
1161 cache[prev].next = next;
1162 return next;
1167 Pop the head item from the list starting at *head, returning the index of the
1168 item, or -1 if the list is already empty.
1170 static inline int lla_pop_head (int *head)
1172 int i = *head;
1173 if (i != -1)
1174 lla_pop_item(head, i);
1175 return i;
1179 Insert the item at index i before the one at index p.
1181 static inline void lla_insert (int i, int p)
1183 int next = p;
1184 int prev = cache[next].prev;
1185 cache[next].prev = i;
1186 cache[prev].next = i;
1187 cache[i].next = next;
1188 cache[i].prev = prev;
1193 Insert the item at index i at the end of the list starting at *head.
1195 static inline void lla_insert_tail (int *head, int i)
1197 if (*head == -1)
1199 *head = i;
1200 cache[i].next = i;
1201 cache[i].prev = i;
1202 } else
1203 lla_insert(i, *head);
1207 Insert the item at index i before the one at index p.
1209 static inline void lla_insert_after(int i, int p)
1211 p = cache[p].next;
1212 lla_insert(i, p);
1217 Insert the item at index i before the one at index p in the list starting at
1218 *head
1220 static inline void lla_insert_before(int *head, int i, int p)
1222 lla_insert(i, p);
1223 if (*head == p)
1224 *head = i;
1229 Free the used slide at index i, and its buffer, and move it to the free
1230 slides list.
1232 static inline void free_slide(int i)
1234 if (cache[i].hid != empty_slide_hid)
1235 buflib_free(&buf_ctx, cache[i].hid);
1236 cache[i].index = -1;
1237 lla_pop_item(&cache_used, i);
1238 lla_insert_tail(&cache_free, i);
1239 if (cache_used == -1)
1241 cache_right_index = -1;
1242 cache_left_index = -1;
1243 cache_center_index = -1;
1249 Free one slide ranked above the given priority. If no such slide can be found,
1250 return false.
1252 static bool free_slide_prio(int prio)
1254 if (cache_used == -1)
1255 return false;
1256 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1257 int prio_l = cache[l].index < center_index ?
1258 center_index - cache[l].index : 0;
1259 int prio_r = cache[r].index > center_index ?
1260 cache[r].index - center_index : 0;
1261 if (prio_l > prio_r)
1263 i = l;
1264 prio_max = prio_l;
1265 } else {
1266 i = r;
1267 prio_max = prio_r;
1269 if (prio_max > prio)
1271 if (i == cache_left_index)
1272 cache_left_index = cache[i].next;
1273 if (i == cache_right_index)
1274 cache_right_index = cache[i].prev;
1275 free_slide(i);
1276 return true;
1277 } else
1278 return false;
1282 Read the pfraw image given as filename and return the hid of the buffer
1284 int read_pfraw(char* filename, int prio)
1286 struct pfraw_header bmph;
1287 int fh = rb->open(filename, O_RDONLY);
1288 if( fh < 0 )
1289 return empty_slide_hid;
1290 else
1291 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1293 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1294 bmph.width * bmph.height;
1296 int hid;
1297 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1299 if (!hid) {
1300 rb->close( fh );
1301 return 0;
1304 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1306 bm->width = bmph.width;
1307 bm->height = bmph.height;
1308 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1310 int y;
1311 for( y = 0; y < bm->height; y++ )
1313 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1314 data += bm->width;
1316 rb->close( fh );
1317 return hid;
1322 Load the surface for the given slide_index into the cache at cache_index.
1324 static inline bool load_and_prepare_surface(const int slide_index,
1325 const int cache_index,
1326 const int prio)
1328 char tmp_path_name[MAX_PATH+1];
1329 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1330 slide_index);
1332 int hid = read_pfraw(tmp_path_name, prio);
1333 if (!hid)
1334 return false;
1336 cache[cache_index].hid = hid;
1338 if ( cache_index < SLIDE_CACHE_SIZE ) {
1339 cache[cache_index].index = slide_index;
1342 return true;
1347 Load the "next" slide that we can load, freeing old slides if needed, provided
1348 that they are further from center_index than the current slide
1350 bool load_new_slide(void)
1352 int i = -1;
1353 if (cache_center_index != -1)
1355 int next, prev;
1356 if (cache[cache_center_index].index != center_index)
1358 if (cache[cache_center_index].index < center_index)
1360 cache_center_index = seek_right_while(cache_center_index,
1361 cache[next_].index <= center_index);
1362 prev = cache_center_index;
1363 next = cache[cache_center_index].next;
1365 else
1367 cache_center_index = seek_left_while(cache_center_index,
1368 cache[next_].index >= center_index);
1369 next = cache_center_index;
1370 prev = cache[cache_center_index].prev;
1372 if (cache[cache_center_index].index != center_index)
1374 if (cache_free == -1)
1375 free_slide_prio(0);
1376 i = lla_pop_head(&cache_free);
1377 if (!load_and_prepare_surface(center_index, i, 0))
1378 goto fail_and_refree;
1379 if (cache[next].index == -1)
1381 if (cache[prev].index == -1)
1382 goto insert_first_slide;
1383 else
1384 next = cache[prev].next;
1386 lla_insert(i, next);
1387 if (cache[i].index < cache[cache_used].index)
1388 cache_used = i;
1389 cache_center_index = i;
1390 cache_left_index = i;
1391 cache_right_index = i;
1392 return true;
1395 if (cache[cache_left_index].index >
1396 cache[cache_center_index].index)
1397 cache_left_index = cache_center_index;
1398 if (cache[cache_right_index].index <
1399 cache[cache_center_index].index)
1400 cache_right_index = cache_center_index;
1401 cache_left_index = seek_left_while(cache_left_index,
1402 cache[ind_].index - 1 == cache[next_].index);
1403 cache_right_index = seek_right_while(cache_right_index,
1404 cache[ind_].index - 1 == cache[next_].index);
1405 int prio_l = cache[cache_center_index].index -
1406 cache[cache_left_index].index + 1;
1407 int prio_r = cache[cache_right_index].index -
1408 cache[cache_center_index].index + 1;
1409 if ((prio_l < prio_r ||
1410 cache[cache_right_index].index >= number_of_slides) &&
1411 cache[cache_left_index].index > 0)
1413 if (cache_free == -1 && !free_slide_prio(prio_l))
1414 return false;
1415 i = lla_pop_head(&cache_free);
1416 if (load_and_prepare_surface(cache[cache_left_index].index
1417 - 1, i, prio_l))
1419 lla_insert_before(&cache_used, i, cache_left_index);
1420 cache_left_index = i;
1421 return true;
1423 } else if(cache[cache_right_index].index < number_of_slides - 1)
1425 if (cache_free == -1 && !free_slide_prio(prio_r))
1426 return false;
1427 i = lla_pop_head(&cache_free);
1428 if (load_and_prepare_surface(cache[cache_right_index].index
1429 + 1, i, prio_r))
1431 lla_insert_after(i, cache_right_index);
1432 cache_right_index = i;
1433 return true;
1436 } else {
1437 i = lla_pop_head(&cache_free);
1438 if (load_and_prepare_surface(center_index, i, 0))
1440 insert_first_slide:
1441 cache[i].next = i;
1442 cache[i].prev = i;
1443 cache_center_index = i;
1444 cache_left_index = i;
1445 cache_right_index = i;
1446 cache_used = i;
1447 return true;
1450 fail_and_refree:
1451 if (i != -1)
1453 lla_insert_tail(&cache_free, i);
1455 return false;
1460 Get a slide from the buffer
1462 static inline struct dim *get_slide(const int hid)
1464 if (!hid)
1465 return NULL;
1467 struct dim *bmp;
1469 bmp = buflib_get_data(&buf_ctx, hid);
1471 return bmp;
1476 Return the requested surface
1478 static inline struct dim *surface(const int slide_index)
1480 if (slide_index < 0)
1481 return 0;
1482 if (slide_index >= number_of_slides)
1483 return 0;
1484 int i;
1485 if ((i = cache_used ) != -1)
1487 do {
1488 if (cache[i].index == slide_index)
1489 return get_slide(cache[i].hid);
1490 i = cache[i].next;
1491 } while (i != cache_used);
1493 return get_slide(empty_slide_hid);
1497 adjust slides so that they are in "steady state" position
1499 void reset_slides(void)
1501 center_slide.angle = 0;
1502 center_slide.cx = 0;
1503 center_slide.cy = 0;
1504 center_slide.distance = 0;
1505 center_slide.slide_index = center_index;
1507 int i;
1508 for (i = 0; i < num_slides; i++) {
1509 struct slide_data *si = &left_slides[i];
1510 si->angle = itilt;
1511 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1512 si->cy = offsetY;
1513 si->slide_index = center_index - 1 - i;
1514 si->distance = 0;
1517 for (i = 0; i < num_slides; i++) {
1518 struct slide_data *si = &right_slides[i];
1519 si->angle = -itilt;
1520 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1521 si->cy = offsetY;
1522 si->slide_index = center_index + 1 + i;
1523 si->distance = 0;
1529 Updates look-up table and other stuff necessary for the rendering.
1530 Call this when the viewport size or slide dimension is changed.
1532 * To calculate the offset that will provide the proper margin, we use the same
1533 * projection used to render the slides. The solution for xc, the slide center,
1534 * is:
1535 * xp * (zo + xs * sin(r))
1536 * xc = xp - xs * cos(r) + ───────────────────────
1538 * TODO: support moving the side slides toward or away from the camera
1540 void recalc_offsets(void)
1542 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1543 PFreal zo;
1544 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1545 PFREAL_ONE) * zoom / 100;
1546 PFreal cosr, sinr;
1548 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1549 cosr = fcos(-itilt);
1550 sinr = fsin(-itilt);
1551 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1552 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1553 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1554 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1555 / CAM_DIST;
1556 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1561 Fade the given color by spreading the fb_data (ushort)
1562 to an uint, multiply and compress the result back to a ushort.
1564 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1565 static inline unsigned fade_color(pix_t c, unsigned a)
1567 unsigned int result;
1568 c = swap16(c);
1569 a = (a + 2) & 0x1fc;
1570 result = ((c & 0xf81f) * a) & 0xf81f00;
1571 result |= ((c & 0x7e0) * a) & 0x7e000;
1572 result >>= 8;
1573 return swap16(result);
1575 #elif LCD_PIXELFORMAT == RGB565
1576 static inline unsigned fade_color(pix_t c, unsigned a)
1578 unsigned int result;
1579 a = (a + 2) & 0x1fc;
1580 result = ((c & 0xf81f) * a) & 0xf81f00;
1581 result |= ((c & 0x7e0) * a) & 0x7e000;
1582 result >>= 8;
1583 return result;
1585 #else
1586 static inline unsigned fade_color(pix_t c, unsigned a)
1588 unsigned val = c;
1589 return MULUQ(val, a) >> 8;
1591 #endif
1594 * Render a single slide
1595 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1596 * on the slide from its center, zo is the slide's depth offset from the plane
1597 * of the display, r is the angle at which the slide is tilted, and xp is the
1598 * point on the display corresponding to xs on the slide, the projection
1599 * formulas are:
1601 * z * (xc + xs * cos(r))
1602 * xp = ──────────────────────
1603 * z + zo + xs * sin(r)
1605 * z * (xc - xp) - xp * zo
1606 * xs = ────────────────────────
1607 * xp * sin(r) - z * cos(r)
1609 * We use the xp projection once, to find the left edge of the slide on the
1610 * display. From there, we use the xs reverse projection to find the horizontal
1611 * offset from the slide center of each column on the screen, until we reach
1612 * the right edge of the slide, or the screen. The reverse projection can be
1613 * optimized by saving the numerator and denominator of the fraction, which can
1614 * then be incremented by (z + zo) and sin(r) respectively.
1616 void render_slide(struct slide_data *slide, const int alpha)
1618 struct dim *bmp = surface(slide->slide_index);
1619 if (!bmp) {
1620 return;
1622 if (slide->angle > 255 || slide->angle < -255)
1623 return;
1624 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1626 const int sw = bmp->width;
1627 const int sh = bmp->height;
1628 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1629 const int w = LCD_WIDTH;
1631 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1633 if (alpha == 256) { /* opaque -> copy table */
1634 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1635 } else { /* precalculate faded table */
1636 int i, lalpha;
1637 for (i = 0; i < REFLECT_HEIGHT; i++) {
1638 lalpha = reflect_table[i];
1639 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1643 PFreal cosr = fcos(slide->angle);
1644 PFreal sinr = fsin(slide->angle);
1645 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1646 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1647 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1648 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1649 (CAM_DIST_R + zo + fmul(xs,sinr)));
1651 /* Since we're finding the screen position of the left edge of the slide,
1652 * we round up.
1654 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1655 >> PFREAL_SHIFT;
1656 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1657 if (xi >= w) {
1658 return;
1660 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1661 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1662 xs = fdiv(xsnum, xsden);
1664 xsnumi = -CAM_DIST_R - zo;
1665 xsdeni = sinr;
1666 int x;
1667 int dy = PFREAL_ONE;
1668 for (x = xi; x < w; x++) {
1669 int column = (xs - slide_left) / PFREAL_ONE;
1670 if (column >= sw)
1671 break;
1672 if (zo || slide->angle)
1673 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1675 const pix_t *ptr = &src[column * bmp->height];
1676 const int pixelstep = BUFFER_WIDTH;
1678 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1679 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1680 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1682 if (alpha == 256) {
1683 while (p >= plim) {
1684 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1685 p -= dy;
1686 pixel -= pixelstep;
1688 } else {
1689 while (p >= plim) {
1690 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1691 p -= dy;
1692 pixel -= pixelstep;
1695 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1696 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1697 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1698 p + (LCD_HEIGHT/2) * dy);
1699 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1701 if (alpha == 256) {
1702 while (p < plim) {
1703 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1704 p += dy;
1705 pixel += pixelstep;
1707 } else {
1708 while (p < plim) {
1709 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1710 p += dy;
1711 pixel += pixelstep;
1714 while (p < plim2) {
1715 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1716 int lalpha = reftab[ty];
1717 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1718 p += dy;
1719 pixel += pixelstep;
1722 if (zo || slide->angle)
1724 xsnum += xsnumi;
1725 xsden += xsdeni;
1726 xs = fdiv(xsnum, xsden);
1727 } else
1728 xs += PFREAL_ONE;
1731 /* let the music play... */
1732 rb->yield();
1733 return;
1738 Jump the the given slide_index
1740 static inline void set_current_slide(const int slide_index)
1742 int old_center_index = center_index;
1743 step = 0;
1744 center_index = fbound(slide_index, 0, number_of_slides - 1);
1745 if (old_center_index != center_index)
1746 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1747 target = center_index;
1748 slide_frame = slide_index << 16;
1749 reset_slides();
1753 Start the animation for changing slides
1755 void start_animation(void)
1757 step = (target < center_slide.slide_index) ? -1 : 1;
1758 pf_state = pf_scrolling;
1762 Go to the previous slide
1764 void show_previous_slide(void)
1766 if (step == 0) {
1767 if (center_index > 0) {
1768 target = center_index - 1;
1769 start_animation();
1771 } else if ( step > 0 ) {
1772 target = center_index;
1773 start_animation();
1774 } else {
1775 target = fmax(0, center_index - 2);
1781 Go to the next slide
1783 void show_next_slide(void)
1785 if (step == 0) {
1786 if (center_index < number_of_slides - 1) {
1787 target = center_index + 1;
1788 start_animation();
1790 } else if ( step < 0 ) {
1791 target = center_index;
1792 start_animation();
1793 } else {
1794 target = fmin(center_index + 2, number_of_slides - 1);
1800 Render the slides. Updates only the offscreen buffer.
1802 void render_all_slides(void)
1804 MYLCD(set_background)(G_BRIGHT(0));
1805 /* TODO: Optimizes this by e.g. invalidating rects */
1806 MYLCD(clear_display)();
1808 int nleft = num_slides;
1809 int nright = num_slides;
1811 int index;
1812 if (step == 0) {
1813 /* no animation, boring plain rendering */
1814 for (index = nleft - 2; index >= 0; index--) {
1815 int alpha = (index < nleft - 2) ? 256 : 128;
1816 alpha -= extra_fade;
1817 if (alpha > 0 )
1818 render_slide(&left_slides[index], alpha);
1820 for (index = nright - 2; index >= 0; index--) {
1821 int alpha = (index < nright - 2) ? 256 : 128;
1822 alpha -= extra_fade;
1823 if (alpha > 0 )
1824 render_slide(&right_slides[index], alpha);
1826 } else {
1827 /* the first and last slide must fade in/fade out */
1828 for (index = nleft - 1; index >= 0; index--) {
1829 int alpha = 256;
1830 if (index == nleft - 1)
1831 alpha = (step > 0) ? 0 : 128 - fade / 2;
1832 if (index == nleft - 2)
1833 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1834 if (index == nleft - 3)
1835 alpha = (step > 0) ? 256 - fade / 2 : 256;
1836 render_slide(&left_slides[index], alpha);
1838 for (index = nright - 1; index >= 0; index--) {
1839 int alpha = (index < nright - 2) ? 256 : 128;
1840 if (index == nright - 1)
1841 alpha = (step > 0) ? fade / 2 : 0;
1842 if (index == nright - 2)
1843 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1844 if (index == nright - 3)
1845 alpha = (step > 0) ? 256 : 128 + fade / 2;
1846 render_slide(&right_slides[index], alpha);
1849 render_slide(&center_slide, 256);
1854 Updates the animation effect. Call this periodically from a timer.
1856 void update_scroll_animation(void)
1858 if (step == 0)
1859 return;
1861 int speed = 16384;
1862 int i;
1864 /* deaccelerate when approaching the target */
1865 if (true) {
1866 const int max = 2 * 65536;
1868 int fi = slide_frame;
1869 fi -= (target << 16);
1870 if (fi < 0)
1871 fi = -fi;
1872 fi = fmin(fi, max);
1874 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1875 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1878 slide_frame += speed * step;
1880 int index = slide_frame >> 16;
1881 int pos = slide_frame & 0xffff;
1882 int neg = 65536 - pos;
1883 int tick = (step < 0) ? neg : pos;
1884 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1886 /* the leftmost and rightmost slide must fade away */
1887 fade = pos / 256;
1889 if (step < 0)
1890 index++;
1891 if (center_index != index) {
1892 center_index = index;
1893 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1894 slide_frame = index << 16;
1895 center_slide.slide_index = center_index;
1896 for (i = 0; i < num_slides; i++)
1897 left_slides[i].slide_index = center_index - 1 - i;
1898 for (i = 0; i < num_slides; i++)
1899 right_slides[i].slide_index = center_index + 1 + i;
1902 center_slide.angle = (step * tick * itilt) >> 16;
1903 center_slide.cx = -step * fmul(offsetX, ftick);
1904 center_slide.cy = fmul(offsetY, ftick);
1906 if (center_index == target) {
1907 reset_slides();
1908 pf_state = pf_idle;
1909 step = 0;
1910 fade = 256;
1911 return;
1914 for (i = 0; i < num_slides; i++) {
1915 struct slide_data *si = &left_slides[i];
1916 si->angle = itilt;
1917 si->cx =
1918 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1919 * slide_spacing * ftick);
1920 si->cy = offsetY;
1923 for (i = 0; i < num_slides; i++) {
1924 struct slide_data *si = &right_slides[i];
1925 si->angle = -itilt;
1926 si->cx =
1927 offsetX + slide_spacing * i * PFREAL_ONE - step
1928 * slide_spacing * ftick;
1929 si->cy = offsetY;
1932 if (step > 0) {
1933 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1934 right_slides[0].angle = -(neg * itilt) >> 16;
1935 right_slides[0].cx = fmul(offsetX, ftick);
1936 right_slides[0].cy = fmul(offsetY, ftick);
1937 } else {
1938 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1939 left_slides[0].angle = (pos * itilt) >> 16;
1940 left_slides[0].cx = -fmul(offsetX, ftick);
1941 left_slides[0].cy = fmul(offsetY, ftick);
1944 /* must change direction ? */
1945 if (target < index)
1946 if (step > 0)
1947 step = -1;
1948 if (target > index)
1949 if (step < 0)
1950 step = 1;
1955 Cleanup the plugin
1957 void cleanup(void *parameter)
1959 (void) parameter;
1960 /* Turn on backlight timeout (revert to settings) */
1961 backlight_use_settings(); /* backlight control in lib/helper.c */
1963 #ifdef USEGSLIB
1964 grey_release();
1965 #endif
1969 Create the "?" slide, that is shown while loading
1970 or when no cover was found.
1972 int create_empty_slide(bool force)
1974 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1975 struct bitmap input_bmp;
1976 int ret;
1977 input_bmp.width = DISPLAY_WIDTH;
1978 input_bmp.height = DISPLAY_HEIGHT;
1979 #if LCD_DEPTH > 1
1980 input_bmp.format = FORMAT_NATIVE;
1981 #endif
1982 input_bmp.data = (char*)buf;
1983 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1984 buf_size,
1985 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1986 &format_transposed);
1987 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1988 return false;
1991 return true;
1995 Shows the album name setting menu
1997 int album_name_menu(void)
1999 int selection = show_album_name;
2001 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2002 "Hide album title", "Show at the bottom", "Show at the top");
2003 rb->do_menu(&album_name_menu, &selection, NULL, false);
2005 show_album_name = selection;
2006 return GO_TO_PREVIOUS;
2010 Shows the settings menu
2012 int settings_menu(void)
2014 int selection = 0;
2015 bool old_val;
2017 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2018 "Spacing", "Centre margin", "Number of slides", "Zoom",
2019 "Show album title", "Resize Covers", "Rebuild cache");
2021 do {
2022 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2023 switch(selection) {
2024 case 0:
2025 rb->set_bool("Show FPS", &show_fps);
2026 reset_track_list();
2027 break;
2029 case 1:
2030 rb->set_int("Spacing between slides", "", 1,
2031 &slide_spacing,
2032 NULL, 1, 0, 100, NULL );
2033 recalc_offsets();
2034 reset_slides();
2035 break;
2037 case 2:
2038 rb->set_int("Centre margin", "", 1,
2039 &center_margin,
2040 NULL, 1, 0, 80, NULL );
2041 recalc_offsets();
2042 reset_slides();
2043 break;
2045 case 3:
2046 rb->set_int("Number of slides", "", 1, &num_slides,
2047 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2048 recalc_offsets();
2049 reset_slides();
2050 break;
2052 case 4:
2053 rb->set_int("Zoom", "", 1, &zoom,
2054 NULL, 1, 10, 300, NULL );
2055 recalc_offsets();
2056 reset_slides();
2057 break;
2058 case 5:
2059 album_name_menu();
2060 reset_track_list();
2061 recalc_offsets();
2062 reset_slides();
2063 break;
2064 case 6:
2065 old_val = resize;
2066 rb->set_bool("Resize Covers", &resize);
2067 if (old_val == resize) /* changed? */
2068 break;
2069 /* fallthrough if changed, since cache needs to be rebuilt */
2070 case 7:
2071 cache_version = 0;
2072 rb->remove(EMPTY_SLIDE);
2073 rb->splash(HZ, "Cache will be rebuilt on next restart");
2074 break;
2076 case MENU_ATTACHED_USB:
2077 return PLUGIN_USB_CONNECTED;
2079 } while ( selection >= 0 );
2080 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2081 return 0;
2085 Show the main menu
2087 int main_menu(void)
2089 int selection = 0;
2090 int result;
2092 #if LCD_DEPTH > 1
2093 rb->lcd_set_foreground(N_BRIGHT(255));
2094 #endif
2096 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2097 "Settings", "Return", "Quit");
2098 while (1) {
2099 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2100 case 0:
2101 result = settings_menu();
2102 if ( result != 0 ) return result;
2103 break;
2105 case 1:
2106 return 0;
2108 case 2:
2109 return -1;
2111 case MENU_ATTACHED_USB:
2112 return PLUGIN_USB_CONNECTED;
2114 default:
2115 return 0;
2121 Animation step for zooming into the current cover
2123 void update_cover_in_animation(void)
2125 cover_animation_keyframe++;
2126 if( cover_animation_keyframe < 20 ) {
2127 center_slide.distance-=5;
2128 center_slide.angle+=1;
2129 extra_fade += 13;
2131 else if( cover_animation_keyframe < 35 ) {
2132 center_slide.angle+=16;
2134 else {
2135 cover_animation_keyframe = 0;
2136 pf_state = pf_show_tracks;
2141 Animation step for zooming out the current cover
2143 void update_cover_out_animation(void)
2145 cover_animation_keyframe++;
2146 if( cover_animation_keyframe <= 15 ) {
2147 center_slide.angle-=16;
2149 else if( cover_animation_keyframe < 35 ) {
2150 center_slide.distance+=5;
2151 center_slide.angle-=1;
2152 extra_fade -= 13;
2154 else {
2155 cover_animation_keyframe = 0;
2156 pf_state = pf_idle;
2161 Draw a blue gradient at y with height h
2163 static inline void draw_gradient(int y, int h)
2165 static int r, inc, c;
2166 inc = (100 << 8) / h;
2167 c = 0;
2168 selected_track_pulse = (selected_track_pulse+1) % 10;
2169 int c2 = selected_track_pulse - 5;
2170 for (r=0; r<h; r++) {
2171 #ifdef HAVE_LCD_COLOR
2172 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2173 c2+250-(c >> 8)));
2174 #else
2175 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2176 #endif
2177 MYLCD(hline)(0, LCD_WIDTH, r+y);
2178 if ( r > h/2 )
2179 c-=inc;
2180 else
2181 c+=inc;
2186 static void track_list_yh(int char_height)
2188 switch (show_album_name)
2190 case album_name_hide:
2191 track_list_y = (show_fps ? char_height : 0);
2192 track_list_h = LCD_HEIGHT - track_list_y;
2193 break;
2194 case album_name_bottom:
2195 track_list_y = (show_fps ? char_height : 0);
2196 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2197 break;
2198 default: /* case album_name_top */
2199 track_list_y = char_height * 2;
2200 track_list_h = LCD_HEIGHT - track_list_y -
2201 (show_fps ? char_height : 0);
2202 break;
2207 Reset the track list after a album change
2209 void reset_track_list(void)
2211 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2212 track_list_yh(albumtxt_h);
2213 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2214 start_index_track_list = 0;
2215 track_scroll_index = 0;
2216 track_scroll_dir = 1;
2217 selected_track = 0;
2219 /* let the tracklist start more centered
2220 * if the screen isn't filled with tracks */
2221 if (track_count*albumtxt_h < track_list_h)
2223 track_list_h = track_count * albumtxt_h;
2224 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2229 Display the list of tracks
2231 void show_track_list(void)
2233 MYLCD(clear_display)();
2234 if ( center_slide.slide_index != track_index ) {
2235 create_track_index(center_slide.slide_index);
2236 reset_track_list();
2238 static int titletxt_w, titletxt_x, color, titletxt_h;
2239 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2241 int titletxt_y = track_list_y;
2242 int track_i;
2243 track_i = start_index_track_list;
2244 for (;track_i < track_list_visible_entries+start_index_track_list;
2245 track_i++)
2247 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2248 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2249 if ( track_i == selected_track ) {
2250 draw_gradient(titletxt_y, titletxt_h);
2251 MYLCD(set_foreground)(G_BRIGHT(255));
2252 if (titletxt_w > LCD_WIDTH ) {
2253 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2254 track_scroll_dir = 1;
2255 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2256 track_scroll_index += track_scroll_dir*2;
2257 titletxt_x = track_scroll_index;
2259 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2261 else {
2262 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2263 MYLCD(set_foreground)(G_BRIGHT(color));
2264 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2266 titletxt_y += titletxt_h;
2270 void select_next_track(void)
2272 if ( selected_track < track_count - 1 ) {
2273 selected_track++;
2274 track_scroll_index = 0;
2275 track_scroll_dir = 1;
2276 if (selected_track==(track_list_visible_entries+start_index_track_list))
2277 start_index_track_list++;
2281 void select_prev_track(void)
2283 if (selected_track > 0 ) {
2284 if (selected_track==start_index_track_list) start_index_track_list--;
2285 track_scroll_index = 0;
2286 track_scroll_dir = 1;
2287 selected_track--;
2292 Draw the current album name
2294 void draw_album_text(void)
2296 if (0 == show_album_name)
2297 return;
2299 int albumtxt_w, albumtxt_h;
2300 int albumtxt_y = 0;
2302 char *albumtxt;
2303 int c;
2304 /* Draw album text */
2305 if ( pf_state == pf_scrolling ) {
2306 c = ((slide_frame & 0xffff )/ 255);
2307 if (step < 0) c = 255-c;
2308 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2309 albumtxt = get_album_name(center_index+step);
2310 c = (c-128)*2;
2312 else {
2313 albumtxt = get_album_name(center_index);
2314 c = (128-c)*2;
2317 else {
2318 c= 255;
2319 albumtxt = get_album_name(center_index);
2322 MYLCD(set_foreground)(G_BRIGHT(c));
2323 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2324 if (center_index != prev_center_index) {
2325 albumtxt_x = 0;
2326 albumtxt_dir = -1;
2327 prev_center_index = center_index;
2330 if (show_album_name == album_name_top)
2331 albumtxt_y = albumtxt_h / 2;
2332 else
2333 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2335 if (albumtxt_w > LCD_WIDTH ) {
2336 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2337 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2338 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2339 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2340 albumtxt_x += albumtxt_dir;
2343 else {
2344 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2352 Main function that also contain the main plasma
2353 algorithm.
2355 int main(void)
2357 int ret;
2359 rb->lcd_setfont(FONT_UI);
2360 draw_splashscreen();
2362 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2363 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2364 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2365 return PLUGIN_ERROR;
2369 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2371 init_reflect_table();
2373 ALIGN_BUFFER(buf, buf_size, 4);
2374 ret = create_album_index();
2375 if (ret == ERROR_BUFFER_FULL) {
2376 rb->splash(HZ, "Not enough memory for album names");
2377 return PLUGIN_ERROR;
2378 } else if (ret == ERROR_NO_ALBUMS) {
2379 rb->splash(HZ, "No albums found. Please enable database");
2380 return PLUGIN_ERROR;
2383 ALIGN_BUFFER(buf, buf_size, 4);
2384 number_of_slides = album_count;
2385 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2386 rb->splash(HZ, "Could not create album art cache");
2387 return PLUGIN_ERROR;
2390 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2391 rb->splash(HZ, "Could not load the empty slide");
2392 return PLUGIN_ERROR;
2394 cache_version = CACHE_VERSION;
2395 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2398 #ifdef USEGSLIB
2399 long grey_buf_used;
2400 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2401 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2403 rb->splash(HZ, "Greylib init failed!");
2404 return PLUGIN_ERROR;
2406 grey_setfont(FONT_UI);
2407 buf_size -= grey_buf_used;
2408 buf = (void*)(grey_buf_used + (char*)buf);
2409 #endif
2410 buflib_init(&buf_ctx, (void *)buf, buf_size);
2412 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2414 rb->splash(HZ, "Unable to load empty slide image");
2415 return PLUGIN_ERROR;
2418 if (!create_pf_thread()) {
2419 rb->splash(HZ, "Cannot create thread!");
2420 return PLUGIN_ERROR;
2423 int i;
2425 /* initialize */
2426 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2427 cache[i].hid = 0;
2428 cache[i].index = 0;
2429 cache[i].next = i + 1;
2430 cache[i].prev = i - 1;
2432 cache[0].prev = i - 1;
2433 cache[i - 1].next = 0;
2434 cache_free = 0;
2435 buffer = LCD_BUF;
2437 pf_state = pf_idle;
2439 track_index = -1;
2440 extra_fade = 0;
2441 slide_frame = 0;
2442 step = 0;
2443 target = 0;
2444 fade = 256;
2446 recalc_offsets();
2447 reset_slides();
2449 char fpstxt[10];
2450 int button;
2452 int frames = 0;
2453 long last_update = *rb->current_tick;
2454 long current_update;
2455 long update_interval = 100;
2456 int fps = 0;
2457 int fpstxt_y;
2459 bool instant_update;
2460 #ifdef USEGSLIB
2461 grey_show(true);
2462 grey_set_drawmode(DRMODE_FG);
2463 #endif
2464 rb->lcd_set_drawmode(DRMODE_FG);
2465 while (true) {
2466 current_update = *rb->current_tick;
2467 frames++;
2469 /* Initial rendering */
2470 instant_update = false;
2472 /* Handle states */
2473 switch ( pf_state ) {
2474 case pf_scrolling:
2475 update_scroll_animation();
2476 render_all_slides();
2477 instant_update = true;
2478 break;
2479 case pf_cover_in:
2480 update_cover_in_animation();
2481 render_all_slides();
2482 instant_update = true;
2483 break;
2484 case pf_cover_out:
2485 update_cover_out_animation();
2486 render_all_slides();
2487 instant_update = true;
2488 break;
2489 case pf_show_tracks:
2490 show_track_list();
2491 break;
2492 case pf_idle:
2493 render_all_slides();
2494 break;
2497 /* Calculate FPS */
2498 if (current_update - last_update > update_interval) {
2499 fps = frames * HZ / (current_update - last_update);
2500 last_update = current_update;
2501 frames = 0;
2503 /* Draw FPS */
2504 if (show_fps)
2506 #ifdef USEGSLIB
2507 MYLCD(set_foreground)(G_BRIGHT(255));
2508 #else
2509 MYLCD(set_foreground)(G_PIX(255,0,0));
2510 #endif
2511 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2512 if (show_album_name == album_name_top)
2513 fpstxt_y = LCD_HEIGHT -
2514 rb->screens[SCREEN_MAIN]->getcharheight();
2515 else
2516 fpstxt_y = 0;
2517 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2519 draw_album_text();
2522 /* Copy offscreen buffer to LCD and give time to other threads */
2523 MYLCD(update)();
2524 rb->yield();
2526 /*/ Handle buttons */
2527 button = rb->get_custom_action(CONTEXT_CUSTOM|
2528 (pf_state == pf_show_tracks ? 1 : 0),
2529 instant_update ? 0 : HZ/16,
2530 get_context_map);
2532 switch (button) {
2533 case PF_QUIT:
2534 return PLUGIN_OK;
2536 case PF_BACK:
2537 if ( pf_state == pf_show_tracks )
2539 buflib_buffer_in(&buf_ctx, borrowed);
2540 borrowed = 0;
2541 track_index = -1;
2542 pf_state = pf_cover_out;
2544 if (pf_state == pf_idle || pf_state == pf_scrolling)
2545 return PLUGIN_OK;
2546 break;
2548 case PF_MENU:
2549 #ifdef USEGSLIB
2550 grey_show(false);
2551 #endif
2552 ret = main_menu();
2553 if ( ret == -1 ) return PLUGIN_OK;
2554 if ( ret != 0 ) return ret;
2555 #ifdef USEGSLIB
2556 grey_show(true);
2557 #endif
2558 MYLCD(set_drawmode)(DRMODE_FG);
2559 break;
2561 case PF_NEXT:
2562 case PF_NEXT_REPEAT:
2563 if ( pf_state == pf_show_tracks )
2564 select_next_track();
2565 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2566 show_next_slide();
2567 break;
2569 case PF_PREV:
2570 case PF_PREV_REPEAT:
2571 if ( pf_state == pf_show_tracks )
2572 select_prev_track();
2573 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2574 show_previous_slide();
2575 break;
2577 case PF_SELECT:
2578 if ( pf_state == pf_idle ) {
2579 pf_state = pf_cover_in;
2581 break;
2583 default:
2584 if (rb->default_event_handler_ex(button, cleanup, NULL)
2585 == SYS_USB_CONNECTED)
2586 return PLUGIN_USB_CONNECTED;
2587 break;
2594 /*************************** Plugin entry point ****************************/
2596 enum plugin_status plugin_start(const void *parameter)
2598 int ret;
2599 (void) parameter;
2600 #if LCD_DEPTH > 1
2601 rb->lcd_set_backdrop(NULL);
2602 #endif
2603 /* Turn off backlight timeout */
2604 backlight_force_on(); /* backlight control in lib/helper.c */
2605 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2606 rb->cpu_boost(true);
2607 #endif
2608 #if PLUGIN_BUFFER_SIZE > 0x10000
2609 buf = rb->plugin_get_buffer(&buf_size);
2610 #else
2611 buf = rb->plugin_get_audio_buffer(&buf_size);
2612 #endif
2613 ret = main();
2614 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2615 rb->cpu_boost(false);
2616 #endif
2617 if ( ret == PLUGIN_OK ) {
2618 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2619 CONFIG_VERSION))
2621 rb->splash(HZ, "Error writing config.");
2622 ret = PLUGIN_ERROR;
2626 end_pf_thread();
2627 cleanup(NULL);
2628 return ret;