Commit the first part of FS#10263: Starting playback from within pictureflow, by...
[kugel-rb/myfork.git] / apps / plugins / pictureflow / pictureflow.c
blob2ceef7c9cb0b7d078b86c98b5a866c0e3b1f6a1b
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 == LOGIK_DAX_PAD || CONFIG_KEYPAD == MEIZU_M6SL_PAD
68 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
69 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
70 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
71 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
72 #elif CONFIG_KEYPAD == ONDIO_PAD
73 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
74 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
75 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
76 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
77 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
78 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
79 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
80 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
81 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
82 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
83 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
84 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
85 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
86 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
87 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
88 #endif
89 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
92 const struct button_mapping pf_context_buttons[] =
94 #ifdef HAVE_TOUCHSCREEN
95 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
96 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
97 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
98 #endif
99 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
100 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
101 #elif CONFIG_KEYPAD == SANSA_C100_PAD
102 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
103 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
104 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
105 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
106 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
107 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
108 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
109 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
110 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, 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; /* offset to the track name */
257 long seek;
258 int filename_idx; /* offset to the filename in the string */
261 struct rect {
262 int left;
263 int right;
264 int top;
265 int bottom;
268 struct load_slide_event_data {
269 int slide_index;
270 int cache_index;
274 struct pfraw_header {
275 int32_t width; /* bmap width in pixels */
276 int32_t height; /* bmap height in pixels */
279 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
280 album_name_top };
281 static char* show_album_name_conf[] =
283 "hide",
284 "bottom",
285 "top"
288 #define MAX_SPACING 40
289 #define MAX_MARGIN 80
291 /* config values and their defaults */
292 static int slide_spacing = DISPLAY_WIDTH / 4;
293 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
294 static int num_slides = 4;
295 static int zoom = 100;
296 static bool show_fps = false;
297 static bool resize = true;
298 static int cache_version = 0;
299 static int show_album_name = (LCD_HEIGHT > 100)
300 ? album_name_top : album_name_bottom;
302 static struct configdata config[] =
304 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
305 NULL },
306 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
307 NULL },
308 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
309 NULL },
310 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
311 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
312 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
313 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
314 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
315 show_album_name_conf }
318 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
320 /** below we allocate the memory we want to use **/
322 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
323 static uint8_t reflect_table[REFLECT_HEIGHT];
324 static struct slide_data center_slide;
325 static struct slide_data left_slides[MAX_SLIDES_COUNT];
326 static struct slide_data right_slides[MAX_SLIDES_COUNT];
327 static int slide_frame;
328 static int step;
329 static int target;
330 static int fade;
331 static int center_index = 0; /* index of the slide that is in the center */
332 static int itilt;
333 static PFreal offsetX;
334 static PFreal offsetY;
335 static int number_of_slides;
337 static struct slide_cache cache[SLIDE_CACHE_SIZE];
338 static int cache_free;
339 static int cache_used = -1;
340 static int cache_left_index = -1;
341 static int cache_right_index = -1;
342 static int cache_center_index = -1;
344 /* use long for aligning */
345 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
346 /* queue (as array) for scheduling load_surface */
348 static int empty_slide_hid;
350 unsigned int thread_id;
351 struct event_queue thread_q;
353 static struct tagcache_search tcs;
355 static struct buflib_context buf_ctx;
357 static struct album_data *album;
358 static char *album_names;
359 static int album_count;
361 static struct track_data *tracks;
362 static char *track_names;
363 static size_t borrowed = 0;
364 static int track_count;
365 static int track_index;
366 static int selected_track;
367 static int selected_track_pulse;
368 void reset_track_list(void);
370 void * buf;
371 size_t buf_size;
373 static bool thread_is_running;
375 static int cover_animation_keyframe;
376 static int extra_fade;
378 static int albumtxt_x = 0;
379 static int albumtxt_dir = -1;
380 static int prev_center_index = -1;
382 static int start_index_track_list = 0;
383 static int track_list_visible_entries = 0;
384 static int track_list_y;
385 static int track_list_h;
386 static int track_scroll_index = 0;
387 static int track_scroll_dir = 1;
390 Proposals for transitions:
392 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
393 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
395 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
397 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
399 TODO:
400 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
401 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
403 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
405 enum pf_states {
406 pf_idle = 0,
407 pf_scrolling,
408 pf_cover_in,
409 pf_show_tracks,
410 pf_cover_out
413 static int pf_state;
415 /** code */
416 static bool free_slide_prio(int prio);
417 static inline unsigned fade_color(pix_t c, unsigned a);
418 bool save_pfraw(char* filename, struct bitmap *bm);
419 bool load_new_slide(void);
420 int load_surface(int);
422 static inline PFreal fmul(PFreal a, PFreal b)
424 return (a*b) >> PFREAL_SHIFT;
428 * This version preshifts each operand, which is useful when we know how many
429 * of the least significant bits will be empty, or are worried about overflow
430 * in a particular calculation
432 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
434 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
437 /* ARMv5+ has a clz instruction equivalent to our function.
439 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
440 static inline int clz(uint32_t v)
442 return __builtin_clz(v);
445 /* Otherwise, use our clz, which can be inlined */
446 #elif defined(CPU_COLDFIRE)
447 /* This clz is based on the log2(n) implementation at
448 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
449 * A clz benchmark plugin showed this to be about 14% faster on coldfire
450 * than the LUT-based version.
452 static inline int clz(uint32_t v)
454 int r = 32;
455 if (v >= 0x10000)
457 v >>= 16;
458 r -= 16;
460 if (v & 0xff00)
462 v >>= 8;
463 r -= 8;
465 if (v & 0xf0)
467 v >>= 4;
468 r -= 4;
470 if (v & 0xc)
472 v >>= 2;
473 r -= 2;
475 if (v & 2)
477 v >>= 1;
478 r -= 1;
480 r -= v;
481 return r;
483 #else
484 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
485 0, 0, 0, 0, 0, 0, 0, 0 };
486 /* This clz is based on the log2(n) implementation at
487 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
488 * It is not any faster than the one above, but trades 16B in the lookup table
489 * for a savings of 12B per each inlined call.
491 static inline int clz(uint32_t v)
493 int r = 28;
494 if (v >= 0x10000)
496 v >>= 16;
497 r -= 16;
499 if (v & 0xff00)
501 v >>= 8;
502 r -= 8;
504 if (v & 0xf0)
506 v >>= 4;
507 r -= 4;
509 return r + clz_lut[v];
511 #endif
513 /* Return the maximum possible left shift for a signed int32, without
514 * overflow
516 static inline int allowed_shift(int32_t val)
518 uint32_t uval = val ^ (val >> 31);
519 return clz(uval) - 1;
522 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
523 * num and den before dividing.
525 static inline PFreal fdiv(PFreal num, PFreal den)
527 int shift = allowed_shift(num);
528 shift = MIN(PFREAL_SHIFT, shift);
529 num <<= shift;
530 den >>= PFREAL_SHIFT - shift;
531 return num / den;
534 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
535 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
536 #define fabs(a) (a < 0 ? -a : a)
537 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
539 #if CONFIG_CPU == SH7034
540 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
541 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
542 #else
543 #define MULUQ(a, b) ((a) * (b))
544 #endif
547 #if 0
548 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
549 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
551 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
552 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
554 static inline PFreal fmul(PFreal a, PFreal b)
556 return (a*b) >> PFREAL_SHIFT;
559 static inline PFreal fdiv(PFreal n, PFreal m)
561 return (n<<(PFREAL_SHIFT))/m;
563 #endif
565 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
566 static const short sin_tab[] = {
567 0, 100, 200, 297, 392, 483, 569, 650,
568 724, 792, 851, 903, 946, 980, 1004, 1019,
569 1024, 1019, 1004, 980, 946, 903, 851, 792,
570 724, 650, 569, 483, 392, 297, 200, 100,
571 0, -100, -200, -297, -392, -483, -569, -650,
572 -724, -792, -851, -903, -946, -980, -1004, -1019,
573 -1024, -1019, -1004, -980, -946, -903, -851, -792,
574 -724, -650, -569, -483, -392, -297, -200, -100,
578 static inline PFreal fsin(int iangle)
580 iangle &= IANGLE_MASK;
582 int i = (iangle >> 4);
583 PFreal p = sin_tab[i];
584 PFreal q = sin_tab[(i+1)];
585 PFreal g = (q - p);
586 return p + g * (iangle-i*16)/16;
589 static inline PFreal fcos(int iangle)
591 return fsin(iangle + (IANGLE_MAX >> 2));
594 static inline unsigned scale_val(unsigned val, unsigned bits)
596 val = val * ((1 << bits) - 1);
597 return ((val >> 8) + val + 128) >> 8;
600 static void output_row_8_transposed(uint32_t row, void * row_in,
601 struct scaler_context *ctx)
603 pix_t *dest = (pix_t*)ctx->bm->data + row;
604 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
605 #ifdef USEGSLIB
606 uint8_t *qp = (uint8_t*)row_in;
607 for (; dest < end; dest += ctx->bm->height)
608 *dest = *qp++;
609 #else
610 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
611 unsigned r, g, b;
612 for (; dest < end; dest += ctx->bm->height)
614 r = scale_val(qp->red, 5);
615 g = scale_val(qp->green, 6);
616 b = scale_val((qp++)->blue, 5);
617 *dest = LCD_RGBPACK_LCD(r,g,b);
619 #endif
622 static void output_row_32_transposed(uint32_t row, void * row_in,
623 struct scaler_context *ctx)
625 pix_t *dest = (pix_t*)ctx->bm->data + row;
626 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
627 #ifdef USEGSLIB
628 uint32_t *qp = (uint32_t*)row_in;
629 for (; dest < end; dest += ctx->bm->height)
630 *dest = SC_OUT(*qp++, ctx);
631 #else
632 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
633 int r, g, b;
634 for (; dest < end; dest += ctx->bm->height)
636 r = scale_val(SC_OUT(qp->r, ctx), 5);
637 g = scale_val(SC_OUT(qp->g, ctx), 6);
638 b = scale_val(SC_OUT(qp->b, ctx), 5);
639 qp++;
640 *dest = LCD_RGBPACK_LCD(r,g,b);
642 #endif
645 #ifdef HAVE_LCD_COLOR
646 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
647 struct scaler_context *ctx)
649 pix_t *dest = (pix_t*)ctx->bm->data + row;
650 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
651 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
652 for (; dest < end; dest += ctx->bm->height)
654 unsigned r, g, b, y, u, v;
655 y = SC_OUT(qp->b, ctx);
656 u = SC_OUT(qp->g, ctx);
657 v = SC_OUT(qp->r, ctx);
658 qp++;
659 yuv_to_rgb(y, u, v, &r, &g, &b);
660 r = scale_val(r, 5);
661 g = scale_val(g, 6);
662 b = scale_val(b, 5);
663 *dest = LCD_RGBPACK_LCD(r, g, b);
666 #endif
668 static unsigned int get_size(struct bitmap *bm)
670 return bm->width * bm->height * sizeof(pix_t);
673 const struct custom_format format_transposed = {
674 .output_row_8 = output_row_8_transposed,
675 #ifdef HAVE_LCD_COLOR
676 .output_row_32 = {
677 output_row_32_transposed,
678 output_row_32_transposed_fromyuv
680 #else
681 .output_row_32 = output_row_32_transposed,
682 #endif
683 .get_size = get_size
686 static const struct button_mapping* get_context_map(int context)
688 return pf_contexts[context & ~CONTEXT_CUSTOM];
691 /* Create the lookup table with the scaling values for the reflections */
692 void init_reflect_table(void)
694 int i;
695 for (i = 0; i < REFLECT_HEIGHT; i++)
696 reflect_table[i] =
697 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
698 (5 * REFLECT_HEIGHT);
702 Create an index of all albums from the database.
703 Also store the album names so we can access them later.
705 int create_album_index(void)
707 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
708 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
709 album_count = 0;
710 rb->tagcache_search(&tcs, tag_album);
711 unsigned int l, old_l = 0;
712 album_names = buf;
713 album[0].name_idx = 0;
714 while (rb->tagcache_get_next(&tcs))
716 buf_size -= sizeof(struct album_data);
717 l = tcs.result_len;
718 if ( album_count > 0 )
719 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
721 if ( l > buf_size )
722 /* not enough memory */
723 return ERROR_BUFFER_FULL;
725 rb->strcpy(buf, tcs.result);
726 buf_size -= l;
727 buf = l + (char *)buf;
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;
762 char* get_track_filename(const int track_index)
764 if ( track_index < track_count )
765 return track_names + tracks[track_index].filename_idx;
766 return 0;
770 Compare two unsigned ints passed via pointers.
772 int compare_tracks (const void *a_v, const void *b_v)
774 uint32_t a = ((struct track_data *)a_v)->sort;
775 uint32_t b = ((struct track_data *)b_v)->sort;
776 return (int)(a - b);
780 Create the track index of the given slide_index.
782 void create_track_index(const int slide_index)
784 if ( slide_index == track_index )
785 return;
786 track_index = slide_index;
788 if (!rb->tagcache_search(&tcs, tag_title))
789 goto fail;
791 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
792 track_count=0;
793 int string_index = 0, track_num;
794 int disc_num;
795 size_t out = 0;
796 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
797 borrowed += out;
798 int avail = borrowed;
799 tracks = (struct track_data*)(track_names + borrowed);
800 while (rb->tagcache_get_next(&tcs))
802 int len = 0, fn_idx = 0, remain;
804 avail -= sizeof(struct track_data);
805 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
806 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
808 if (disc_num < 0)
809 disc_num = 0;
810 retry:
811 if (track_num >= 0)
813 if (disc_num)
814 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
815 "%d.%02d: %s", disc_num, track_num + 1, tcs.result);
816 else
817 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
818 "%d: %s", track_num + 1, tcs.result);
820 else
822 track_num = 0;
823 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
824 "%s", tcs.result);
826 if (fn_idx <= 0)
827 goto fail;
828 remain = avail - fn_idx;
829 if (remain >= MAX_PATH)
830 { /* retrieve filename for building the playlist */
831 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
832 track_names + string_index + fn_idx, remain);
833 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
834 /* make sure track name and file name are really split by a \0, else
835 * get_track_name might fail */
836 *(track_names + string_index + fn_idx -1) = '\0';
839 else /* request more buffer so that track and filename fit */
840 len = (avail - remain) + MAX_PATH;
841 if (len > avail)
843 while (len > avail)
845 if (!free_slide_prio(0))
846 goto fail;
847 out = 0;
848 buflib_buffer_out(&buf_ctx, &out);
849 avail += out;
850 borrowed += out;
851 if (track_count)
853 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
854 unsigned int bytes = track_count * sizeof(struct track_data);
855 rb->memmove(new_tracks, tracks, bytes);
856 tracks = new_tracks;
859 goto retry;
862 avail -= len;
863 tracks--;
864 tracks->sort = ((disc_num - 1) << 24) + (track_num << 14) + track_count;
865 tracks->name_idx = string_index;
866 tracks->seek = tcs.result_seek;
867 tracks->filename_idx = fn_idx + string_index;
868 track_count++;
869 string_index += len;
872 rb->tagcache_search_finish(&tcs);
874 /* now fix the track list order */
875 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
876 return;
877 fail:
878 track_count = 0;
879 return;
883 Determine filename of the album art for the given slide_index and
884 store the result in buf.
885 The algorithm looks for the first track of the given album uses
886 find_albumart to find the filename.
888 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
889 int buflen)
891 if ( slide_index == -1 )
893 rb->strncpy( buf, EMPTY_SLIDE, buflen );
896 if (!rb->tagcache_search(&tcs, tag_filename))
897 return false;
899 bool result;
900 /* find the first track of the album */
901 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
903 if ( rb->tagcache_get_next(&tcs) ) {
904 struct mp3entry id3;
905 int fd;
907 #ifdef HAVE_TC_RAMCACHE
908 if (rb->tagcache_fill_tags(&id3, tcs.result))
910 rb->strncpy(id3.path, tcs.result, sizeof(id3.path));
911 id3.path[sizeof(id3.path) - 1] = 0;
913 else
914 #endif
916 fd = rb->open(tcs.result, O_RDONLY);
917 rb->get_metadata(&id3, fd, tcs.result);
918 rb->close(fd);
920 if ( search_albumart_files(&id3, ":", buf, buflen) )
921 result = true;
922 else
923 result = false;
925 else {
926 /* did not find a matching track */
927 result = false;
929 rb->tagcache_search_finish(&tcs);
930 return result;
934 Draw the PictureFlow logo
936 void draw_splashscreen(void)
938 unsigned char * buf_tmp = buf;
939 size_t buf_tmp_size = buf_size;
940 struct screen* display = rb->screens[0];
941 #if FB_DATA_SZ > 1
942 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
943 #endif
944 struct bitmap logo = {
945 #if LCD_WIDTH < 200
946 .width = 100,
947 .height = 18,
948 #else
949 .width = 193,
950 .height = 34,
951 #endif
952 .data = buf_tmp
954 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size, FORMAT_NATIVE,
955 NULL);
956 #if LCD_DEPTH > 1
957 rb->lcd_set_background(N_BRIGHT(0));
958 rb->lcd_set_foreground(N_BRIGHT(255));
959 #else
960 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
961 #endif
962 rb->lcd_clear_display();
964 if (ret > 0)
966 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
967 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
968 #endif
969 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
970 logo.width, logo.height);
971 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
972 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
973 #endif
976 rb->lcd_update();
981 Draw a simple progress bar
983 void draw_progressbar(int step)
985 int txt_w, txt_h;
986 const int bar_height = 22;
987 const int w = LCD_WIDTH - 20;
988 const int x = 10;
990 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
992 int y = (LCD_HEIGHT - txt_h)/2;
994 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
995 y += (txt_h + 5);
997 #if LCD_DEPTH > 1
998 rb->lcd_set_foreground(N_BRIGHT(100));
999 #endif
1000 rb->lcd_drawrect(x, y, w+2, bar_height);
1001 #if LCD_DEPTH > 1
1002 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1003 #endif
1005 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1006 #if LCD_DEPTH > 1
1007 rb->lcd_set_foreground(N_BRIGHT(255));
1008 #endif
1009 rb->lcd_update();
1010 rb->yield();
1014 Precomupte the album art images and store them in CACHE_PREFIX.
1016 bool create_albumart_cache(void)
1018 int ret;
1020 int i, slides = 0;
1021 struct bitmap input_bmp;
1023 char pfraw_file[MAX_PATH];
1024 char albumart_file[MAX_PATH];
1025 unsigned int format = FORMAT_NATIVE;
1026 cache_version = 0;
1027 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1028 if (resize)
1029 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1030 for (i=0; i < album_count; i++)
1032 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
1034 /* delete existing cache, so it's a true rebuild */
1035 if(rb->file_exists(pfraw_file))
1036 rb->remove(pfraw_file);
1037 draw_progressbar(i);
1038 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1039 continue;
1041 input_bmp.data = buf;
1042 input_bmp.width = DISPLAY_WIDTH;
1043 input_bmp.height = DISPLAY_HEIGHT;
1044 ret = read_image_file(albumart_file, &input_bmp,
1045 buf_size, format, &format_transposed);
1046 if (ret <= 0) {
1047 rb->splash(HZ, "Could not read bmp");
1048 continue; /* skip missing/broken files */
1050 if (!save_pfraw(pfraw_file, &input_bmp))
1052 rb->splash(HZ, "Could not write bmp");
1054 slides++;
1055 if ( rb->button_get(false) == PF_MENU ) return false;
1057 if ( slides == 0 ) {
1058 /* Warn the user that we couldn't find any albumart */
1059 rb->splash(2*HZ, "No album art found");
1060 return false;
1062 return true;
1066 Thread used for loading and preparing bitmaps in the background
1068 void thread(void)
1070 long sleep_time = 5 * HZ;
1071 struct queue_event ev;
1072 while (1) {
1073 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1074 switch (ev.id) {
1075 case EV_EXIT:
1076 return;
1077 case EV_WAKEUP:
1078 /* we just woke up */
1079 break;
1081 while ( load_new_slide() ) {
1082 rb->yield();
1083 switch (ev.id) {
1084 case EV_EXIT:
1085 return;
1093 End the thread by posting the EV_EXIT event
1095 void end_pf_thread(void)
1097 if ( thread_is_running ) {
1098 rb->queue_post(&thread_q, EV_EXIT, 0);
1099 rb->thread_wait(thread_id);
1100 /* remove the thread's queue from the broadcast list */
1101 rb->queue_delete(&thread_q);
1102 thread_is_running = false;
1109 Create the thread an setup the event queue
1111 bool create_pf_thread(void)
1113 /* put the thread's queue in the bcast list */
1114 rb->queue_init(&thread_q, true);
1115 if ((thread_id = rb->create_thread(
1116 thread,
1117 thread_stack,
1118 sizeof(thread_stack),
1120 "Picture load thread"
1121 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1122 PRIORITY_REALTIME + 1))
1123 IF_COP(, CPU)
1125 ) == 0) {
1126 return false;
1128 thread_is_running = true;
1129 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1130 return true;
1134 Safe the given bitmap as filename in the pfraw format
1136 bool save_pfraw(char* filename, struct bitmap *bm)
1138 struct pfraw_header bmph;
1139 bmph.width = bm->width;
1140 bmph.height = bm->height;
1141 int fh = rb->creat( filename );
1142 if( fh < 0 ) return false;
1143 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1144 int y;
1145 for( y = 0; y < bm->height; y++ )
1147 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1148 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1150 rb->close( fh );
1151 return true;
1156 * The following functions implement the linked-list-in-array used to manage
1157 * the LRU cache of slides, and the list of free cache slots.
1160 #define seek_right_while(start, cond) \
1161 ({ \
1162 int ind_, next_ = (start); \
1163 do { \
1164 ind_ = next_; \
1165 next_ = cache[ind_].next; \
1166 } while (next_ != cache_used && (cond)); \
1167 ind_; \
1170 #define seek_left_while(start, cond) \
1171 ({ \
1172 int ind_, next_ = (start); \
1173 do { \
1174 ind_ = next_; \
1175 next_ = cache[ind_].prev; \
1176 } while (ind_ != cache_used && (cond)); \
1177 ind_; \
1181 Pop the given item from the linked list starting at *head, returning the next
1182 item, or -1 if the list is now empty.
1184 static inline int lla_pop_item (int *head, int i)
1186 int prev = cache[i].prev;
1187 int next = cache[i].next;
1188 if (i == next)
1190 *head = -1;
1191 return -1;
1193 else if (i == *head)
1194 *head = next;
1195 cache[next].prev = prev;
1196 cache[prev].next = next;
1197 return next;
1202 Pop the head item from the list starting at *head, returning the index of the
1203 item, or -1 if the list is already empty.
1205 static inline int lla_pop_head (int *head)
1207 int i = *head;
1208 if (i != -1)
1209 lla_pop_item(head, i);
1210 return i;
1214 Insert the item at index i before the one at index p.
1216 static inline void lla_insert (int i, int p)
1218 int next = p;
1219 int prev = cache[next].prev;
1220 cache[next].prev = i;
1221 cache[prev].next = i;
1222 cache[i].next = next;
1223 cache[i].prev = prev;
1228 Insert the item at index i at the end of the list starting at *head.
1230 static inline void lla_insert_tail (int *head, int i)
1232 if (*head == -1)
1234 *head = i;
1235 cache[i].next = i;
1236 cache[i].prev = i;
1237 } else
1238 lla_insert(i, *head);
1242 Insert the item at index i before the one at index p.
1244 static inline void lla_insert_after(int i, int p)
1246 p = cache[p].next;
1247 lla_insert(i, p);
1252 Insert the item at index i before the one at index p in the list starting at
1253 *head
1255 static inline void lla_insert_before(int *head, int i, int p)
1257 lla_insert(i, p);
1258 if (*head == p)
1259 *head = i;
1264 Free the used slide at index i, and its buffer, and move it to the free
1265 slides list.
1267 static inline void free_slide(int i)
1269 if (cache[i].hid != empty_slide_hid)
1270 buflib_free(&buf_ctx, cache[i].hid);
1271 cache[i].index = -1;
1272 lla_pop_item(&cache_used, i);
1273 lla_insert_tail(&cache_free, i);
1274 if (cache_used == -1)
1276 cache_right_index = -1;
1277 cache_left_index = -1;
1278 cache_center_index = -1;
1284 Free one slide ranked above the given priority. If no such slide can be found,
1285 return false.
1287 static bool free_slide_prio(int prio)
1289 if (cache_used == -1)
1290 return false;
1291 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1292 int prio_l = cache[l].index < center_index ?
1293 center_index - cache[l].index : 0;
1294 int prio_r = cache[r].index > center_index ?
1295 cache[r].index - center_index : 0;
1296 if (prio_l > prio_r)
1298 i = l;
1299 prio_max = prio_l;
1300 } else {
1301 i = r;
1302 prio_max = prio_r;
1304 if (prio_max > prio)
1306 if (i == cache_left_index)
1307 cache_left_index = cache[i].next;
1308 if (i == cache_right_index)
1309 cache_right_index = cache[i].prev;
1310 free_slide(i);
1311 return true;
1312 } else
1313 return false;
1317 Read the pfraw image given as filename and return the hid of the buffer
1319 int read_pfraw(char* filename, int prio)
1321 struct pfraw_header bmph;
1322 int fh = rb->open(filename, O_RDONLY);
1323 if( fh < 0 )
1324 return empty_slide_hid;
1325 else
1326 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1328 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1329 bmph.width * bmph.height;
1331 int hid;
1332 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1334 if (!hid) {
1335 rb->close( fh );
1336 return 0;
1339 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1341 bm->width = bmph.width;
1342 bm->height = bmph.height;
1343 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1345 int y;
1346 for( y = 0; y < bm->height; y++ )
1348 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1349 data += bm->width;
1351 rb->close( fh );
1352 return hid;
1357 Load the surface for the given slide_index into the cache at cache_index.
1359 static inline bool load_and_prepare_surface(const int slide_index,
1360 const int cache_index,
1361 const int prio)
1363 char tmp_path_name[MAX_PATH+1];
1364 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1365 slide_index);
1367 int hid = read_pfraw(tmp_path_name, prio);
1368 if (!hid)
1369 return false;
1371 cache[cache_index].hid = hid;
1373 if ( cache_index < SLIDE_CACHE_SIZE ) {
1374 cache[cache_index].index = slide_index;
1377 return true;
1382 Load the "next" slide that we can load, freeing old slides if needed, provided
1383 that they are further from center_index than the current slide
1385 bool load_new_slide(void)
1387 int i = -1;
1388 if (cache_center_index != -1)
1390 int next, prev;
1391 if (cache[cache_center_index].index != center_index)
1393 if (cache[cache_center_index].index < center_index)
1395 cache_center_index = seek_right_while(cache_center_index,
1396 cache[next_].index <= center_index);
1397 prev = cache_center_index;
1398 next = cache[cache_center_index].next;
1400 else
1402 cache_center_index = seek_left_while(cache_center_index,
1403 cache[next_].index >= center_index);
1404 next = cache_center_index;
1405 prev = cache[cache_center_index].prev;
1407 if (cache[cache_center_index].index != center_index)
1409 if (cache_free == -1)
1410 free_slide_prio(0);
1411 i = lla_pop_head(&cache_free);
1412 if (!load_and_prepare_surface(center_index, i, 0))
1413 goto fail_and_refree;
1414 if (cache[next].index == -1)
1416 if (cache[prev].index == -1)
1417 goto insert_first_slide;
1418 else
1419 next = cache[prev].next;
1421 lla_insert(i, next);
1422 if (cache[i].index < cache[cache_used].index)
1423 cache_used = i;
1424 cache_center_index = i;
1425 cache_left_index = i;
1426 cache_right_index = i;
1427 return true;
1430 if (cache[cache_left_index].index >
1431 cache[cache_center_index].index)
1432 cache_left_index = cache_center_index;
1433 if (cache[cache_right_index].index <
1434 cache[cache_center_index].index)
1435 cache_right_index = cache_center_index;
1436 cache_left_index = seek_left_while(cache_left_index,
1437 cache[ind_].index - 1 == cache[next_].index);
1438 cache_right_index = seek_right_while(cache_right_index,
1439 cache[ind_].index - 1 == cache[next_].index);
1440 int prio_l = cache[cache_center_index].index -
1441 cache[cache_left_index].index + 1;
1442 int prio_r = cache[cache_right_index].index -
1443 cache[cache_center_index].index + 1;
1444 if ((prio_l < prio_r ||
1445 cache[cache_right_index].index >= number_of_slides) &&
1446 cache[cache_left_index].index > 0)
1448 if (cache_free == -1 && !free_slide_prio(prio_l))
1449 return false;
1450 i = lla_pop_head(&cache_free);
1451 if (load_and_prepare_surface(cache[cache_left_index].index
1452 - 1, i, prio_l))
1454 lla_insert_before(&cache_used, i, cache_left_index);
1455 cache_left_index = i;
1456 return true;
1458 } else if(cache[cache_right_index].index < number_of_slides - 1)
1460 if (cache_free == -1 && !free_slide_prio(prio_r))
1461 return false;
1462 i = lla_pop_head(&cache_free);
1463 if (load_and_prepare_surface(cache[cache_right_index].index
1464 + 1, i, prio_r))
1466 lla_insert_after(i, cache_right_index);
1467 cache_right_index = i;
1468 return true;
1471 } else {
1472 i = lla_pop_head(&cache_free);
1473 if (load_and_prepare_surface(center_index, i, 0))
1475 insert_first_slide:
1476 cache[i].next = i;
1477 cache[i].prev = i;
1478 cache_center_index = i;
1479 cache_left_index = i;
1480 cache_right_index = i;
1481 cache_used = i;
1482 return true;
1485 fail_and_refree:
1486 if (i != -1)
1488 lla_insert_tail(&cache_free, i);
1490 return false;
1495 Get a slide from the buffer
1497 static inline struct dim *get_slide(const int hid)
1499 if (!hid)
1500 return NULL;
1502 struct dim *bmp;
1504 bmp = buflib_get_data(&buf_ctx, hid);
1506 return bmp;
1511 Return the requested surface
1513 static inline struct dim *surface(const int slide_index)
1515 if (slide_index < 0)
1516 return 0;
1517 if (slide_index >= number_of_slides)
1518 return 0;
1519 int i;
1520 if ((i = cache_used ) != -1)
1522 do {
1523 if (cache[i].index == slide_index)
1524 return get_slide(cache[i].hid);
1525 i = cache[i].next;
1526 } while (i != cache_used);
1528 return get_slide(empty_slide_hid);
1532 adjust slides so that they are in "steady state" position
1534 void reset_slides(void)
1536 center_slide.angle = 0;
1537 center_slide.cx = 0;
1538 center_slide.cy = 0;
1539 center_slide.distance = 0;
1540 center_slide.slide_index = center_index;
1542 int i;
1543 for (i = 0; i < num_slides; i++) {
1544 struct slide_data *si = &left_slides[i];
1545 si->angle = itilt;
1546 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1547 si->cy = offsetY;
1548 si->slide_index = center_index - 1 - i;
1549 si->distance = 0;
1552 for (i = 0; i < num_slides; i++) {
1553 struct slide_data *si = &right_slides[i];
1554 si->angle = -itilt;
1555 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1556 si->cy = offsetY;
1557 si->slide_index = center_index + 1 + i;
1558 si->distance = 0;
1564 Updates look-up table and other stuff necessary for the rendering.
1565 Call this when the viewport size or slide dimension is changed.
1567 * To calculate the offset that will provide the proper margin, we use the same
1568 * projection used to render the slides. The solution for xc, the slide center,
1569 * is:
1570 * xp * (zo + xs * sin(r))
1571 * xc = xp - xs * cos(r) + ───────────────────────
1573 * TODO: support moving the side slides toward or away from the camera
1575 void recalc_offsets(void)
1577 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1578 PFreal zo;
1579 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1580 PFREAL_ONE) * zoom / 100;
1581 PFreal cosr, sinr;
1583 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1584 cosr = fcos(-itilt);
1585 sinr = fsin(-itilt);
1586 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1587 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1588 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1589 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1590 / CAM_DIST;
1591 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1596 Fade the given color by spreading the fb_data (ushort)
1597 to an uint, multiply and compress the result back to a ushort.
1599 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1600 static inline unsigned fade_color(pix_t c, unsigned a)
1602 unsigned int result;
1603 c = swap16(c);
1604 a = (a + 2) & 0x1fc;
1605 result = ((c & 0xf81f) * a) & 0xf81f00;
1606 result |= ((c & 0x7e0) * a) & 0x7e000;
1607 result >>= 8;
1608 return swap16(result);
1610 #elif LCD_PIXELFORMAT == RGB565
1611 static inline unsigned fade_color(pix_t c, unsigned a)
1613 unsigned int result;
1614 a = (a + 2) & 0x1fc;
1615 result = ((c & 0xf81f) * a) & 0xf81f00;
1616 result |= ((c & 0x7e0) * a) & 0x7e000;
1617 result >>= 8;
1618 return result;
1620 #else
1621 static inline unsigned fade_color(pix_t c, unsigned a)
1623 unsigned val = c;
1624 return MULUQ(val, a) >> 8;
1626 #endif
1629 * Render a single slide
1630 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1631 * on the slide from its center, zo is the slide's depth offset from the plane
1632 * of the display, r is the angle at which the slide is tilted, and xp is the
1633 * point on the display corresponding to xs on the slide, the projection
1634 * formulas are:
1636 * z * (xc + xs * cos(r))
1637 * xp = ──────────────────────
1638 * z + zo + xs * sin(r)
1640 * z * (xc - xp) - xp * zo
1641 * xs = ────────────────────────
1642 * xp * sin(r) - z * cos(r)
1644 * We use the xp projection once, to find the left edge of the slide on the
1645 * display. From there, we use the xs reverse projection to find the horizontal
1646 * offset from the slide center of each column on the screen, until we reach
1647 * the right edge of the slide, or the screen. The reverse projection can be
1648 * optimized by saving the numerator and denominator of the fraction, which can
1649 * then be incremented by (z + zo) and sin(r) respectively.
1651 void render_slide(struct slide_data *slide, const int alpha)
1653 struct dim *bmp = surface(slide->slide_index);
1654 if (!bmp) {
1655 return;
1657 if (slide->angle > 255 || slide->angle < -255)
1658 return;
1659 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1661 const int sw = bmp->width;
1662 const int sh = bmp->height;
1663 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1664 const int w = LCD_WIDTH;
1666 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1668 if (alpha == 256) { /* opaque -> copy table */
1669 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1670 } else { /* precalculate faded table */
1671 int i, lalpha;
1672 for (i = 0; i < REFLECT_HEIGHT; i++) {
1673 lalpha = reflect_table[i];
1674 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1678 PFreal cosr = fcos(slide->angle);
1679 PFreal sinr = fsin(slide->angle);
1680 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1681 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1682 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1683 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1684 (CAM_DIST_R + zo + fmul(xs,sinr)));
1686 /* Since we're finding the screen position of the left edge of the slide,
1687 * we round up.
1689 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1690 >> PFREAL_SHIFT;
1691 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1692 if (xi >= w) {
1693 return;
1695 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1696 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1697 xs = fdiv(xsnum, xsden);
1699 xsnumi = -CAM_DIST_R - zo;
1700 xsdeni = sinr;
1701 int x;
1702 int dy = PFREAL_ONE;
1703 for (x = xi; x < w; x++) {
1704 int column = (xs - slide_left) / PFREAL_ONE;
1705 if (column >= sw)
1706 break;
1707 if (zo || slide->angle)
1708 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1710 const pix_t *ptr = &src[column * bmp->height];
1711 const int pixelstep = BUFFER_WIDTH;
1713 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1714 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1715 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1717 if (alpha == 256) {
1718 while (p >= plim) {
1719 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1720 p -= dy;
1721 pixel -= pixelstep;
1723 } else {
1724 while (p >= plim) {
1725 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1726 p -= dy;
1727 pixel -= pixelstep;
1730 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1731 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1732 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1733 p + (LCD_HEIGHT/2) * dy);
1734 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1736 if (alpha == 256) {
1737 while (p < plim) {
1738 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1739 p += dy;
1740 pixel += pixelstep;
1742 } else {
1743 while (p < plim) {
1744 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1745 p += dy;
1746 pixel += pixelstep;
1749 while (p < plim2) {
1750 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1751 int lalpha = reftab[ty];
1752 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1753 p += dy;
1754 pixel += pixelstep;
1757 if (zo || slide->angle)
1759 xsnum += xsnumi;
1760 xsden += xsdeni;
1761 xs = fdiv(xsnum, xsden);
1762 } else
1763 xs += PFREAL_ONE;
1766 /* let the music play... */
1767 rb->yield();
1768 return;
1773 Jump the the given slide_index
1775 static inline void set_current_slide(const int slide_index)
1777 int old_center_index = center_index;
1778 step = 0;
1779 center_index = fbound(slide_index, 0, number_of_slides - 1);
1780 if (old_center_index != center_index)
1781 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1782 target = center_index;
1783 slide_frame = slide_index << 16;
1784 reset_slides();
1788 Start the animation for changing slides
1790 void start_animation(void)
1792 step = (target < center_slide.slide_index) ? -1 : 1;
1793 pf_state = pf_scrolling;
1797 Go to the previous slide
1799 void show_previous_slide(void)
1801 if (step == 0) {
1802 if (center_index > 0) {
1803 target = center_index - 1;
1804 start_animation();
1806 } else if ( step > 0 ) {
1807 target = center_index;
1808 start_animation();
1809 } else {
1810 target = fmax(0, center_index - 2);
1816 Go to the next slide
1818 void show_next_slide(void)
1820 if (step == 0) {
1821 if (center_index < number_of_slides - 1) {
1822 target = center_index + 1;
1823 start_animation();
1825 } else if ( step < 0 ) {
1826 target = center_index;
1827 start_animation();
1828 } else {
1829 target = fmin(center_index + 2, number_of_slides - 1);
1835 Render the slides. Updates only the offscreen buffer.
1837 void render_all_slides(void)
1839 MYLCD(set_background)(G_BRIGHT(0));
1840 /* TODO: Optimizes this by e.g. invalidating rects */
1841 MYLCD(clear_display)();
1843 int nleft = num_slides;
1844 int nright = num_slides;
1846 int index;
1847 if (step == 0) {
1848 /* no animation, boring plain rendering */
1849 for (index = nleft - 2; index >= 0; index--) {
1850 int alpha = (index < nleft - 2) ? 256 : 128;
1851 alpha -= extra_fade;
1852 if (alpha > 0 )
1853 render_slide(&left_slides[index], alpha);
1855 for (index = nright - 2; index >= 0; index--) {
1856 int alpha = (index < nright - 2) ? 256 : 128;
1857 alpha -= extra_fade;
1858 if (alpha > 0 )
1859 render_slide(&right_slides[index], alpha);
1861 } else {
1862 /* the first and last slide must fade in/fade out */
1863 for (index = nleft - 1; index >= 0; index--) {
1864 int alpha = 256;
1865 if (index == nleft - 1)
1866 alpha = (step > 0) ? 0 : 128 - fade / 2;
1867 if (index == nleft - 2)
1868 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1869 if (index == nleft - 3)
1870 alpha = (step > 0) ? 256 - fade / 2 : 256;
1871 render_slide(&left_slides[index], alpha);
1873 for (index = nright - 1; index >= 0; index--) {
1874 int alpha = (index < nright - 2) ? 256 : 128;
1875 if (index == nright - 1)
1876 alpha = (step > 0) ? fade / 2 : 0;
1877 if (index == nright - 2)
1878 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1879 if (index == nright - 3)
1880 alpha = (step > 0) ? 256 : 128 + fade / 2;
1881 render_slide(&right_slides[index], alpha);
1884 render_slide(&center_slide, 256);
1889 Updates the animation effect. Call this periodically from a timer.
1891 void update_scroll_animation(void)
1893 if (step == 0)
1894 return;
1896 int speed = 16384;
1897 int i;
1899 /* deaccelerate when approaching the target */
1900 if (true) {
1901 const int max = 2 * 65536;
1903 int fi = slide_frame;
1904 fi -= (target << 16);
1905 if (fi < 0)
1906 fi = -fi;
1907 fi = fmin(fi, max);
1909 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1910 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1913 slide_frame += speed * step;
1915 int index = slide_frame >> 16;
1916 int pos = slide_frame & 0xffff;
1917 int neg = 65536 - pos;
1918 int tick = (step < 0) ? neg : pos;
1919 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1921 /* the leftmost and rightmost slide must fade away */
1922 fade = pos / 256;
1924 if (step < 0)
1925 index++;
1926 if (center_index != index) {
1927 center_index = index;
1928 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1929 slide_frame = index << 16;
1930 center_slide.slide_index = center_index;
1931 for (i = 0; i < num_slides; i++)
1932 left_slides[i].slide_index = center_index - 1 - i;
1933 for (i = 0; i < num_slides; i++)
1934 right_slides[i].slide_index = center_index + 1 + i;
1937 center_slide.angle = (step * tick * itilt) >> 16;
1938 center_slide.cx = -step * fmul(offsetX, ftick);
1939 center_slide.cy = fmul(offsetY, ftick);
1941 if (center_index == target) {
1942 reset_slides();
1943 pf_state = pf_idle;
1944 step = 0;
1945 fade = 256;
1946 return;
1949 for (i = 0; i < num_slides; i++) {
1950 struct slide_data *si = &left_slides[i];
1951 si->angle = itilt;
1952 si->cx =
1953 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1954 * slide_spacing * ftick);
1955 si->cy = offsetY;
1958 for (i = 0; i < num_slides; i++) {
1959 struct slide_data *si = &right_slides[i];
1960 si->angle = -itilt;
1961 si->cx =
1962 offsetX + slide_spacing * i * PFREAL_ONE - step
1963 * slide_spacing * ftick;
1964 si->cy = offsetY;
1967 if (step > 0) {
1968 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1969 right_slides[0].angle = -(neg * itilt) >> 16;
1970 right_slides[0].cx = fmul(offsetX, ftick);
1971 right_slides[0].cy = fmul(offsetY, ftick);
1972 } else {
1973 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1974 left_slides[0].angle = (pos * itilt) >> 16;
1975 left_slides[0].cx = -fmul(offsetX, ftick);
1976 left_slides[0].cy = fmul(offsetY, ftick);
1979 /* must change direction ? */
1980 if (target < index)
1981 if (step > 0)
1982 step = -1;
1983 if (target > index)
1984 if (step < 0)
1985 step = 1;
1990 Cleanup the plugin
1992 void cleanup(void *parameter)
1994 (void) parameter;
1995 /* Turn on backlight timeout (revert to settings) */
1996 backlight_use_settings(); /* backlight control in lib/helper.c */
1998 #ifdef USEGSLIB
1999 grey_release();
2000 #endif
2004 Create the "?" slide, that is shown while loading
2005 or when no cover was found.
2007 int create_empty_slide(bool force)
2009 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
2010 struct bitmap input_bmp;
2011 int ret;
2012 input_bmp.width = DISPLAY_WIDTH;
2013 input_bmp.height = DISPLAY_HEIGHT;
2014 #if LCD_DEPTH > 1
2015 input_bmp.format = FORMAT_NATIVE;
2016 #endif
2017 input_bmp.data = (char*)buf;
2018 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
2019 buf_size,
2020 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
2021 &format_transposed);
2022 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
2023 return false;
2026 return true;
2030 Shows the album name setting menu
2032 int album_name_menu(void)
2034 int selection = show_album_name;
2036 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
2037 "Hide album title", "Show at the bottom", "Show at the top");
2038 rb->do_menu(&album_name_menu, &selection, NULL, false);
2040 show_album_name = selection;
2041 return GO_TO_PREVIOUS;
2045 Shows the settings menu
2047 int settings_menu(void)
2049 int selection = 0;
2050 bool old_val;
2052 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2053 "Spacing", "Centre margin", "Number of slides", "Zoom",
2054 "Show album title", "Resize Covers", "Rebuild cache");
2056 do {
2057 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2058 switch(selection) {
2059 case 0:
2060 rb->set_bool("Show FPS", &show_fps);
2061 reset_track_list();
2062 break;
2064 case 1:
2065 rb->set_int("Spacing between slides", "", 1,
2066 &slide_spacing,
2067 NULL, 1, 0, 100, NULL );
2068 recalc_offsets();
2069 reset_slides();
2070 break;
2072 case 2:
2073 rb->set_int("Centre margin", "", 1,
2074 &center_margin,
2075 NULL, 1, 0, 80, NULL );
2076 recalc_offsets();
2077 reset_slides();
2078 break;
2080 case 3:
2081 rb->set_int("Number of slides", "", 1, &num_slides,
2082 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2083 recalc_offsets();
2084 reset_slides();
2085 break;
2087 case 4:
2088 rb->set_int("Zoom", "", 1, &zoom,
2089 NULL, 1, 10, 300, NULL );
2090 recalc_offsets();
2091 reset_slides();
2092 break;
2093 case 5:
2094 album_name_menu();
2095 reset_track_list();
2096 recalc_offsets();
2097 reset_slides();
2098 break;
2099 case 6:
2100 old_val = resize;
2101 rb->set_bool("Resize Covers", &resize);
2102 if (old_val == resize) /* changed? */
2103 break;
2104 /* fallthrough if changed, since cache needs to be rebuilt */
2105 case 7:
2106 cache_version = 0;
2107 rb->remove(EMPTY_SLIDE);
2108 rb->splash(HZ, "Cache will be rebuilt on next restart");
2109 break;
2111 case MENU_ATTACHED_USB:
2112 return PLUGIN_USB_CONNECTED;
2114 } while ( selection >= 0 );
2115 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2116 return 0;
2120 Show the main menu
2122 int main_menu(void)
2124 int selection = 0;
2125 int result;
2127 #if LCD_DEPTH > 1
2128 rb->lcd_set_foreground(N_BRIGHT(255));
2129 #endif
2131 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2132 "Settings", "Return", "Quit");
2133 while (1) {
2134 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2135 case 0:
2136 result = settings_menu();
2137 if ( result != 0 ) return result;
2138 break;
2140 case 1:
2141 return 0;
2143 case 2:
2144 return -1;
2146 case MENU_ATTACHED_USB:
2147 return PLUGIN_USB_CONNECTED;
2149 default:
2150 return 0;
2156 Animation step for zooming into the current cover
2158 void update_cover_in_animation(void)
2160 cover_animation_keyframe++;
2161 if( cover_animation_keyframe < 20 ) {
2162 center_slide.distance-=5;
2163 center_slide.angle+=1;
2164 extra_fade += 13;
2166 else if( cover_animation_keyframe < 35 ) {
2167 center_slide.angle+=16;
2169 else {
2170 cover_animation_keyframe = 0;
2171 pf_state = pf_show_tracks;
2176 Animation step for zooming out the current cover
2178 void update_cover_out_animation(void)
2180 cover_animation_keyframe++;
2181 if( cover_animation_keyframe <= 15 ) {
2182 center_slide.angle-=16;
2184 else if( cover_animation_keyframe < 35 ) {
2185 center_slide.distance+=5;
2186 center_slide.angle-=1;
2187 extra_fade -= 13;
2189 else {
2190 cover_animation_keyframe = 0;
2191 pf_state = pf_idle;
2196 Draw a blue gradient at y with height h
2198 static inline void draw_gradient(int y, int h)
2200 static int r, inc, c;
2201 inc = (100 << 8) / h;
2202 c = 0;
2203 selected_track_pulse = (selected_track_pulse+1) % 10;
2204 int c2 = selected_track_pulse - 5;
2205 for (r=0; r<h; r++) {
2206 #ifdef HAVE_LCD_COLOR
2207 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2208 c2+250-(c >> 8)));
2209 #else
2210 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2211 #endif
2212 MYLCD(hline)(0, LCD_WIDTH, r+y);
2213 if ( r > h/2 )
2214 c-=inc;
2215 else
2216 c+=inc;
2221 static void track_list_yh(int char_height)
2223 switch (show_album_name)
2225 case album_name_hide:
2226 track_list_y = (show_fps ? char_height : 0);
2227 track_list_h = LCD_HEIGHT - track_list_y;
2228 break;
2229 case album_name_bottom:
2230 track_list_y = (show_fps ? char_height : 0);
2231 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2232 break;
2233 default: /* case album_name_top */
2234 track_list_y = char_height * 2;
2235 track_list_h = LCD_HEIGHT - track_list_y -
2236 (show_fps ? char_height : 0);
2237 break;
2242 Reset the track list after a album change
2244 void reset_track_list(void)
2246 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2247 track_list_yh(albumtxt_h);
2248 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2249 start_index_track_list = 0;
2250 track_scroll_index = 0;
2251 track_scroll_dir = 1;
2252 selected_track = 0;
2254 /* let the tracklist start more centered
2255 * if the screen isn't filled with tracks */
2256 if (track_count*albumtxt_h < track_list_h)
2258 track_list_h = track_count * albumtxt_h;
2259 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2264 Display the list of tracks
2266 void show_track_list(void)
2268 MYLCD(clear_display)();
2269 if ( center_slide.slide_index != track_index ) {
2270 create_track_index(center_slide.slide_index);
2271 reset_track_list();
2273 static int titletxt_w, titletxt_x, color, titletxt_h;
2274 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2276 int titletxt_y = track_list_y;
2277 int track_i;
2278 track_i = start_index_track_list;
2279 for (;track_i < track_list_visible_entries+start_index_track_list;
2280 track_i++)
2282 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2283 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2284 if ( track_i == selected_track ) {
2285 draw_gradient(titletxt_y, titletxt_h);
2286 MYLCD(set_foreground)(G_BRIGHT(255));
2287 if (titletxt_w > LCD_WIDTH ) {
2288 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2289 track_scroll_dir = 1;
2290 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2291 track_scroll_index += track_scroll_dir*2;
2292 titletxt_x = track_scroll_index;
2294 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2296 else {
2297 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2298 MYLCD(set_foreground)(G_BRIGHT(color));
2299 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2301 titletxt_y += titletxt_h;
2305 void select_next_track(void)
2307 if ( selected_track < track_count - 1 ) {
2308 selected_track++;
2309 track_scroll_index = 0;
2310 track_scroll_dir = 1;
2311 if (selected_track==(track_list_visible_entries+start_index_track_list))
2312 start_index_track_list++;
2316 void select_prev_track(void)
2318 if (selected_track > 0 ) {
2319 if (selected_track==start_index_track_list) start_index_track_list--;
2320 track_scroll_index = 0;
2321 track_scroll_dir = 1;
2322 selected_track--;
2327 * Puts the current tracklist into a newly created playlist and starts playling
2329 void start_playback(void)
2331 static int old_playlist = -1;
2332 if (center_slide.slide_index == old_playlist)
2334 rb->playlist_start(selected_track, 0);
2336 /* First, replace the current playlist with a new one */
2337 else if ((rb->playlist_remove_all_tracks(NULL) == 0) &&
2338 (rb->playlist_create(PLUGIN_DEMOS_DIR, NULL) == 0))
2340 int count = 0;
2341 do {
2342 if (rb->playlist_add(get_track_filename(count)) != 0)
2343 break;
2344 } while(++count < track_count);
2346 rb->playlist_sync(NULL);
2348 rb->playlist_start(selected_track, 0);
2350 old_playlist = center_slide.slide_index;
2355 Draw the current album name
2357 void draw_album_text(void)
2359 if (0 == show_album_name)
2360 return;
2362 int albumtxt_w, albumtxt_h;
2363 int albumtxt_y = 0;
2365 char *albumtxt;
2366 int c;
2367 /* Draw album text */
2368 if ( pf_state == pf_scrolling ) {
2369 c = ((slide_frame & 0xffff )/ 255);
2370 if (step < 0) c = 255-c;
2371 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2372 albumtxt = get_album_name(center_index+step);
2373 c = (c-128)*2;
2375 else {
2376 albumtxt = get_album_name(center_index);
2377 c = (128-c)*2;
2380 else {
2381 c= 255;
2382 albumtxt = get_album_name(center_index);
2385 MYLCD(set_foreground)(G_BRIGHT(c));
2386 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2387 if (center_index != prev_center_index) {
2388 albumtxt_x = 0;
2389 albumtxt_dir = -1;
2390 prev_center_index = center_index;
2393 if (show_album_name == album_name_top)
2394 albumtxt_y = albumtxt_h / 2;
2395 else
2396 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2398 if (albumtxt_w > LCD_WIDTH ) {
2399 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2400 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2401 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2402 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2403 albumtxt_x += albumtxt_dir;
2406 else {
2407 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2414 Display an error message and wait for input.
2416 void error_wait(const char *message)
2418 rb->splashf(0, "%s. Press any button to continue.", message);
2419 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2420 rb->yield();
2421 rb->sleep(2 * HZ);
2425 Main function that also contain the main plasma
2426 algorithm.
2428 int main(void)
2430 int ret;
2432 rb->lcd_setfont(FONT_UI);
2433 draw_splashscreen();
2435 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2436 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2437 error_wait("Could not create directory " CACHE_PREFIX);
2438 return PLUGIN_ERROR;
2442 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2444 init_reflect_table();
2446 ALIGN_BUFFER(buf, buf_size, 4);
2447 ret = create_album_index();
2448 if (ret == ERROR_BUFFER_FULL) {
2449 error_wait("Not enough memory for album names");
2450 return PLUGIN_ERROR;
2451 } else if (ret == ERROR_NO_ALBUMS) {
2452 error_wait("No albums found. Please enable database");
2453 return PLUGIN_ERROR;
2456 ALIGN_BUFFER(buf, buf_size, 4);
2457 number_of_slides = album_count;
2458 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2459 error_wait("Could not create album art cache");
2460 return PLUGIN_ERROR;
2463 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2464 error_wait("Could not load the empty slide");
2465 return PLUGIN_ERROR;
2467 cache_version = CACHE_VERSION;
2468 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2471 #ifdef USEGSLIB
2472 long grey_buf_used;
2473 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2474 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2476 error_wait("Greylib init failed!");
2477 return PLUGIN_ERROR;
2479 grey_setfont(FONT_UI);
2480 buf_size -= grey_buf_used;
2481 buf = (void*)(grey_buf_used + (char*)buf);
2482 #endif
2483 buflib_init(&buf_ctx, (void *)buf, buf_size);
2485 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2487 error_wait("Unable to load empty slide image");
2488 return PLUGIN_ERROR;
2491 if (!create_pf_thread()) {
2492 error_wait("Cannot create thread!");
2493 return PLUGIN_ERROR;
2496 int i;
2498 /* initialize */
2499 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2500 cache[i].hid = 0;
2501 cache[i].index = 0;
2502 cache[i].next = i + 1;
2503 cache[i].prev = i - 1;
2505 cache[0].prev = i - 1;
2506 cache[i - 1].next = 0;
2507 cache_free = 0;
2508 buffer = LCD_BUF;
2510 pf_state = pf_idle;
2512 track_index = -1;
2513 extra_fade = 0;
2514 slide_frame = 0;
2515 step = 0;
2516 target = 0;
2517 fade = 256;
2519 recalc_offsets();
2520 reset_slides();
2522 char fpstxt[10];
2523 int button;
2525 int frames = 0;
2526 long last_update = *rb->current_tick;
2527 long current_update;
2528 long update_interval = 100;
2529 int fps = 0;
2530 int fpstxt_y;
2532 bool instant_update;
2533 #ifdef USEGSLIB
2534 grey_show(true);
2535 grey_set_drawmode(DRMODE_FG);
2536 #endif
2537 rb->lcd_set_drawmode(DRMODE_FG);
2538 while (true) {
2539 current_update = *rb->current_tick;
2540 frames++;
2542 /* Initial rendering */
2543 instant_update = false;
2545 /* Handle states */
2546 switch ( pf_state ) {
2547 case pf_scrolling:
2548 update_scroll_animation();
2549 render_all_slides();
2550 instant_update = true;
2551 break;
2552 case pf_cover_in:
2553 update_cover_in_animation();
2554 render_all_slides();
2555 instant_update = true;
2556 break;
2557 case pf_cover_out:
2558 update_cover_out_animation();
2559 render_all_slides();
2560 instant_update = true;
2561 break;
2562 case pf_show_tracks:
2563 show_track_list();
2564 break;
2565 case pf_idle:
2566 render_all_slides();
2567 break;
2570 /* Calculate FPS */
2571 if (current_update - last_update > update_interval) {
2572 fps = frames * HZ / (current_update - last_update);
2573 last_update = current_update;
2574 frames = 0;
2576 /* Draw FPS */
2577 if (show_fps)
2579 #ifdef USEGSLIB
2580 MYLCD(set_foreground)(G_BRIGHT(255));
2581 #else
2582 MYLCD(set_foreground)(G_PIX(255,0,0));
2583 #endif
2584 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2585 if (show_album_name == album_name_top)
2586 fpstxt_y = LCD_HEIGHT -
2587 rb->screens[SCREEN_MAIN]->getcharheight();
2588 else
2589 fpstxt_y = 0;
2590 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2592 draw_album_text();
2595 /* Copy offscreen buffer to LCD and give time to other threads */
2596 MYLCD(update)();
2597 rb->yield();
2599 /*/ Handle buttons */
2600 button = rb->get_custom_action(CONTEXT_CUSTOM|
2601 (pf_state == pf_show_tracks ? 1 : 0),
2602 instant_update ? 0 : HZ/16,
2603 get_context_map);
2605 switch (button) {
2606 case PF_QUIT:
2607 return PLUGIN_OK;
2609 case PF_BACK:
2610 if ( pf_state == pf_show_tracks )
2612 buflib_buffer_in(&buf_ctx, borrowed);
2613 borrowed = 0;
2614 track_index = -1;
2615 pf_state = pf_cover_out;
2617 if (pf_state == pf_idle || pf_state == pf_scrolling)
2618 return PLUGIN_OK;
2619 break;
2621 case PF_MENU:
2622 #ifdef USEGSLIB
2623 grey_show(false);
2624 #endif
2625 ret = main_menu();
2626 if ( ret == -1 ) return PLUGIN_OK;
2627 if ( ret != 0 ) return ret;
2628 #ifdef USEGSLIB
2629 grey_show(true);
2630 #endif
2631 MYLCD(set_drawmode)(DRMODE_FG);
2632 break;
2634 case PF_NEXT:
2635 case PF_NEXT_REPEAT:
2636 if ( pf_state == pf_show_tracks )
2637 select_next_track();
2638 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2639 show_next_slide();
2640 break;
2642 case PF_PREV:
2643 case PF_PREV_REPEAT:
2644 if ( pf_state == pf_show_tracks )
2645 select_prev_track();
2646 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2647 show_previous_slide();
2648 break;
2650 case PF_SELECT:
2651 if ( pf_state == pf_idle ) {
2652 pf_state = pf_cover_in;
2654 else if ( pf_state == pf_show_tracks ) {
2655 start_playback();
2657 break;
2659 default:
2660 if (rb->default_event_handler_ex(button, cleanup, NULL)
2661 == SYS_USB_CONNECTED)
2662 return PLUGIN_USB_CONNECTED;
2663 break;
2670 /*************************** Plugin entry point ****************************/
2672 enum plugin_status plugin_start(const void *parameter)
2674 int ret;
2675 (void) parameter;
2676 #if LCD_DEPTH > 1
2677 rb->lcd_set_backdrop(NULL);
2678 #endif
2679 /* Turn off backlight timeout */
2680 backlight_force_on(); /* backlight control in lib/helper.c */
2681 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2682 rb->cpu_boost(true);
2683 #endif
2684 #if PLUGIN_BUFFER_SIZE > 0x10000
2685 buf = rb->plugin_get_buffer(&buf_size);
2686 #else
2687 buf = rb->plugin_get_audio_buffer(&buf_size);
2688 #endif
2689 ret = main();
2690 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2691 rb->cpu_boost(false);
2692 #endif
2693 if ( ret == PLUGIN_OK ) {
2694 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2695 CONFIG_VERSION))
2697 rb->splash(HZ, "Error writing config.");
2698 ret = PLUGIN_ERROR;
2702 end_pf_thread();
2703 cleanup(NULL);
2704 return ret;