Clean up create_track_index, fixing FS#9333, and use tagcache-provided length instead...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blob73dca5a485397e78ef96ba237084d9e33246aa95
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/picture.h"
33 #include "pluginbitmaps/pictureflow_logo.h"
34 #include "lib/grey.h"
35 #include "lib/feature_wrappers.h"
36 #include "lib/buflib.h"
38 PLUGIN_HEADER
40 /******************************* Globals ***********************************/
42 #define PF_PREV ACTION_STD_PREV
43 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
44 #define PF_NEXT ACTION_STD_NEXT
45 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
46 #define PF_SELECT ACTION_STD_OK
47 #define PF_CONTEXT ACTION_STD_CONTEXT
48 #define PF_BACK ACTION_STD_CANCEL
49 #define PF_MENU ACTION_STD_MENU
50 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
52 const struct button_mapping pf_context_album_scroll[] =
54 #ifdef HAVE_TOUCHSCREEN
55 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
56 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
57 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
58 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
59 #endif
60 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
61 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
62 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
63 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
64 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
65 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
66 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
67 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
68 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
69 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
70 CONFIG_KEYPAD == MEIZU_M6SL_PAD
71 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
72 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
73 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
74 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
75 #elif CONFIG_KEYPAD == ONDIO_PAD
76 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
77 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
79 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
80 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
81 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
82 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
83 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
84 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
85 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
86 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
87 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
88 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
89 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
90 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
91 #endif
92 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
95 const struct button_mapping pf_context_buttons[] =
97 #ifdef HAVE_TOUCHSCREEN
98 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
99 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
100 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
101 #endif
102 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
103 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
104 #elif CONFIG_KEYPAD == SANSA_C100_PAD
105 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
106 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
107 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
108 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
109 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
110 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
111 CONFIG_KEYPAD == SANSA_FUZE_PAD
112 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
113 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
115 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
116 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
117 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
118 #if CONFIG_KEYPAD == COWOND2_PAD
119 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
120 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
121 #endif
122 #elif CONFIG_KEYPAD == SANSA_E200_PAD
123 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
124 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
125 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
126 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
127 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
128 || (CONFIG_KEYPAD == IPOD_4G_PAD)
129 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
130 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
131 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
132 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
133 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
134 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
135 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
136 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
137 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
138 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
139 #endif
140 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
142 #else
143 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
144 #endif
146 const struct button_mapping *pf_contexts[] =
148 pf_context_album_scroll,
149 pf_context_buttons
152 #if LCD_DEPTH < 8
153 #if LCD_DEPTH > 1
154 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
155 #else /* LCD_DEPTH <= 1 */
156 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
157 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
158 #define PICTUREFLOW_DRMODE DRMODE_SOLID
159 #else
160 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
161 #endif
162 #endif /* LCD_DEPTH <= 1 */
163 #define USEGSLIB
164 GREY_INFO_STRUCT
165 #define LCD_BUF _grey_info.buffer
166 #define MYLCD(fn) grey_ ## fn
167 #define G_PIX(r,g,b) \
168 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
169 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
170 #define G_BRIGHT(y) (y)
171 #define BUFFER_WIDTH _grey_info.width
172 #define BUFFER_HEIGHT _grey_info.height
173 typedef unsigned char pix_t;
174 #else /* LCD_DEPTH >= 8 */
175 #define LCD_BUF rb->lcd_framebuffer
176 #define MYLCD(fn) rb->lcd_ ## fn
177 #define G_PIX LCD_RGBPACK
178 #define N_PIX LCD_RGBPACK
179 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
180 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
181 #define BUFFER_WIDTH LCD_WIDTH
182 #define BUFFER_HEIGHT LCD_HEIGHT
183 typedef fb_data pix_t;
184 #endif /* LCD_DEPTH >= 8 */
186 /* for fixed-point arithmetic, we need minimum 32-bit long
187 long long (64-bit) might be useful for multiplication and division */
188 #define PFreal long
189 #define PFREAL_SHIFT 10
190 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
191 #define PFREAL_ONE (1 << PFREAL_SHIFT)
192 #define PFREAL_HALF (PFREAL_ONE >> 1)
195 #define IANGLE_MAX 1024
196 #define IANGLE_MASK 1023
198 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
199 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
200 #define DISPLAY_HEIGHT REFLECT_TOP
201 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
202 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
203 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
204 (REFLECT_HEIGHT * 5))
205 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
206 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
207 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
208 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
209 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
211 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
213 #define MAX_SLIDES_COUNT 10
215 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
216 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
218 #define EV_EXIT 9999
219 #define EV_WAKEUP 1337
221 /* maximum number of albums */
223 #define MAX_TRACKS 50
224 #define AVG_TRACK_NAME_LENGTH 20
227 #define UNIQBUF_SIZE (64*1024)
229 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
230 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
232 /* Error return values */
233 #define ERROR_NO_ALBUMS -1
234 #define ERROR_BUFFER_FULL -2
236 /* current version for cover cache */
237 #define CACHE_VERSION 2
238 #define CONFIG_VERSION 1
239 #define CONFIG_FILE "pictureflow.cfg"
241 /** structs we use */
243 struct slide_data {
244 int slide_index;
245 int angle;
246 PFreal cx;
247 PFreal cy;
248 PFreal distance;
251 struct slide_cache {
252 int index; /* index of the cached slide */
253 int hid; /* handle ID of the cached slide */
254 short next; /* "next" slide, with LRU last */
255 short prev; /* "previous" slide */
258 struct album_data {
259 int name_idx;
260 long seek;
263 struct track_data {
264 int name_idx;
265 long seek;
268 struct rect {
269 int left;
270 int right;
271 int top;
272 int bottom;
275 struct load_slide_event_data {
276 int slide_index;
277 int cache_index;
281 struct pfraw_header {
282 int32_t width; /* bmap width in pixels */
283 int32_t height; /* bmap height in pixels */
286 const struct picture logos[]={
287 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
290 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
291 album_name_top };
292 static char* show_album_name_conf[] =
294 "hide",
295 "bottom",
296 "top"
299 #define MAX_SPACING 40
300 #define MAX_MARGIN 80
302 /* config values and their defaults */
303 static int slide_spacing = DISPLAY_WIDTH / 4;
304 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
305 static int num_slides = 4;
306 static int zoom = 100;
307 static bool show_fps = false;
308 static bool resize = true;
309 static int cache_version = 0;
310 static int show_album_name = (LCD_HEIGHT > 100)
311 ? album_name_top : album_name_bottom;
313 static struct configdata config[] =
315 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
316 NULL },
317 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
318 NULL },
319 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
320 NULL },
321 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
322 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
323 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
324 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
325 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
326 show_album_name_conf }
329 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
331 /** below we allocate the memory we want to use **/
333 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
334 static uint8_t reflect_table[REFLECT_HEIGHT];
335 static struct slide_data center_slide;
336 static struct slide_data left_slides[MAX_SLIDES_COUNT];
337 static struct slide_data right_slides[MAX_SLIDES_COUNT];
338 static int slide_frame;
339 static int step;
340 static int target;
341 static int fade;
342 static int center_index = 0; /* index of the slide that is in the center */
343 static int itilt;
344 static PFreal offsetX;
345 static PFreal offsetY;
346 static int number_of_slides;
348 static struct slide_cache cache[SLIDE_CACHE_SIZE];
349 static int cache_free;
350 static int cache_used = -1;
351 static int cache_left_index = -1;
352 static int cache_right_index = -1;
353 static int cache_center_index = -1;
355 /* use long for aligning */
356 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
357 /* queue (as array) for scheduling load_surface */
359 static int empty_slide_hid;
361 unsigned int thread_id;
362 struct event_queue thread_q;
364 static struct tagcache_search tcs;
366 static struct buflib_context buf_ctx;
368 static struct album_data *album;
369 static char *album_names;
370 static int album_count;
372 static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
373 static struct track_data tracks[MAX_TRACKS];
374 static int track_count;
375 static int track_index;
376 static int selected_track;
377 static int selected_track_pulse;
378 void reset_track_list(void);
380 void * buf;
381 size_t buf_size;
383 static bool thread_is_running;
385 static int cover_animation_keyframe;
386 static int extra_fade;
388 static int albumtxt_x = 0;
389 static int albumtxt_dir = -1;
390 static int prev_center_index = -1;
392 static int start_index_track_list = 0;
393 static int track_list_visible_entries = 0;
394 static int track_list_y;
395 static int track_list_h;
396 static int track_scroll_index = 0;
397 static int track_scroll_dir = 1;
400 Proposals for transitions:
402 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
403 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
405 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
407 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
409 TODO:
410 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
411 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
413 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
415 enum pf_states {
416 pf_idle = 0,
417 pf_scrolling,
418 pf_cover_in,
419 pf_show_tracks,
420 pf_cover_out
423 static int pf_state;
425 /** code */
426 static inline unsigned fade_color(pix_t c, unsigned a);
427 bool save_pfraw(char* filename, struct bitmap *bm);
428 bool load_new_slide(void);
429 int load_surface(int);
431 static inline PFreal fmul(PFreal a, PFreal b)
433 return (a*b) >> PFREAL_SHIFT;
437 * This version preshifts each operand, which is useful when we know how many
438 * of the least significant bits will be empty, or are worried about overflow
439 * in a particular calculation
441 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
443 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
446 /* ARMv5+ has a clz instruction equivalent to our function.
448 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
449 static inline int clz(uint32_t v)
451 return __builtin_clz(v);
454 /* Otherwise, use our clz, which can be inlined */
455 #elif defined(CPU_COLDFIRE)
456 /* This clz is based on the log2(n) implementation at
457 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
458 * A clz benchmark plugin showed this to be about 14% faster on coldfire
459 * than the LUT-based version.
461 static inline int clz(uint32_t v)
463 int r = 32;
464 if (v >= 0x10000)
466 v >>= 16;
467 r -= 16;
469 if (v & 0xff00)
471 v >>= 8;
472 r -= 8;
474 if (v & 0xf0)
476 v >>= 4;
477 r -= 4;
479 if (v & 0xc)
481 v >>= 2;
482 r -= 2;
484 if (v & 2)
486 v >>= 1;
487 r -= 1;
489 r -= v;
490 return r;
492 #else
493 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
494 0, 0, 0, 0, 0, 0, 0, 0 };
495 /* This clz is based on the log2(n) implementation at
496 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
497 * It is not any faster than the one above, but trades 16B in the lookup table
498 * for a savings of 12B per each inlined call.
500 static inline int clz(uint32_t v)
502 int r = 28;
503 if (v >= 0x10000)
505 v >>= 16;
506 r -= 16;
508 if (v & 0xff00)
510 v >>= 8;
511 r -= 8;
513 if (v & 0xf0)
515 v >>= 4;
516 r -= 4;
518 return r + clz_lut[v];
520 #endif
522 /* Return the maximum possible left shift for a signed int32, without
523 * overflow
525 static inline int allowed_shift(int32_t val)
527 uint32_t uval = val ^ (val >> 31);
528 return clz(uval) - 1;
531 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
532 * num and den before dividing.
534 static inline PFreal fdiv(PFreal num, PFreal den)
536 int shift = allowed_shift(num);
537 shift = MIN(PFREAL_SHIFT, shift);
538 num <<= shift;
539 den >>= PFREAL_SHIFT - shift;
540 return num / den;
543 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
544 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
545 #define fabs(a) (a < 0 ? -a : a)
546 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
548 #if CONFIG_CPU == SH7034
549 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
550 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
551 #else
552 #define MULUQ(a, b) ((a) * (b))
553 #endif
556 #if 0
557 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
558 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
560 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
561 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
563 static inline PFreal fmul(PFreal a, PFreal b)
565 return (a*b) >> PFREAL_SHIFT;
568 static inline PFreal fdiv(PFreal n, PFreal m)
570 return (n<<(PFREAL_SHIFT))/m;
572 #endif
574 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
575 static const short sin_tab[] = {
576 0, 100, 200, 297, 392, 483, 569, 650,
577 724, 792, 851, 903, 946, 980, 1004, 1019,
578 1024, 1019, 1004, 980, 946, 903, 851, 792,
579 724, 650, 569, 483, 392, 297, 200, 100,
580 0, -100, -200, -297, -392, -483, -569, -650,
581 -724, -792, -851, -903, -946, -980, -1004, -1019,
582 -1024, -1019, -1004, -980, -946, -903, -851, -792,
583 -724, -650, -569, -483, -392, -297, -200, -100,
587 static inline PFreal fsin(int iangle)
589 iangle &= IANGLE_MASK;
591 int i = (iangle >> 4);
592 PFreal p = sin_tab[i];
593 PFreal q = sin_tab[(i+1)];
594 PFreal g = (q - p);
595 return p + g * (iangle-i*16)/16;
598 static inline PFreal fcos(int iangle)
600 return fsin(iangle + (IANGLE_MAX >> 2));
603 static inline uint32_t div255(uint32_t val)
605 return ((((val >> 8) + val) >> 8) + val) >> 8;
608 #define SCALE_VAL(val,out) div255((val) * (out) + 127)
610 static void output_row_8_transposed(uint32_t row, void * row_in,
611 struct scaler_context *ctx)
613 pix_t *dest = (pix_t*)ctx->bm->data + row;
614 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
615 #ifdef USEGSLIB
616 uint8_t *qp = (uint8_t*)row_in;
617 for (; dest < end; dest += ctx->bm->height)
618 *dest = *qp++;
619 #else
620 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
621 unsigned r, g, b;
622 for (; dest < end; dest += ctx->bm->height)
624 r = qp->red;
625 g = qp->green;
626 b = (qp++)->blue;
627 *dest = LCD_RGBPACK_LCD(r,g,b);
629 #endif
632 static void output_row_32_transposed(uint32_t row, void * row_in,
633 struct scaler_context *ctx)
635 pix_t *dest = (pix_t*)ctx->bm->data + row;
636 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
637 #ifdef USEGSLIB
638 uint32_t *qp = (uint32_t*)row_in;
639 for (; dest < end; dest += ctx->bm->height)
640 *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor);
641 #else
642 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
643 uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31),
644 rb_rnd = SCALE_VAL(ctx->round, 31),
645 g_mul = SCALE_VAL(ctx->divisor, 63),
646 g_rnd = SCALE_VAL(ctx->round, 63);
647 int r, g, b;
648 for (; dest < end; dest += ctx->bm->height)
650 r = SC_MUL(qp->r + rb_rnd, rb_mul);
651 g = SC_MUL(qp->g + g_rnd, g_mul);
652 b = SC_MUL(qp->b + rb_rnd, rb_mul);
653 qp++;
654 *dest = LCD_RGBPACK_LCD(r,g,b);
656 #endif
659 #ifdef HAVE_LCD_COLOR
660 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
661 struct scaler_context *ctx)
663 pix_t *dest = (pix_t*)ctx->bm->data + row;
664 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
665 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
666 for (; dest < end; dest += ctx->bm->height)
668 unsigned r, g, b, y, u, v;
669 y = SC_MUL(qp->b + ctx->round, ctx->divisor);
670 u = SC_MUL(qp->g + ctx->round, ctx->divisor);
671 v = SC_MUL(qp->r + ctx->round, ctx->divisor);
672 qp++;
673 yuv_to_rgb(y, u, v, &r, &g, &b);
674 r = (31 * r + (r >> 3) + 127) >> 8;
675 g = (63 * g + (g >> 2) + 127) >> 8;
676 b = (31 * b + (b >> 3) + 127) >> 8;
677 *dest = LCD_RGBPACK_LCD(r, g, b);
680 #endif
682 static unsigned int get_size(struct bitmap *bm)
684 return bm->width * bm->height * sizeof(pix_t);
687 const struct custom_format format_transposed = {
688 .output_row_8 = output_row_8_transposed,
689 #ifdef HAVE_LCD_COLOR
690 .output_row_32 = {
691 output_row_32_transposed,
692 output_row_32_transposed_fromyuv
694 #else
695 .output_row_32 = output_row_32_transposed,
696 #endif
697 .get_size = get_size
700 static const struct button_mapping* get_context_map(int context)
702 return pf_contexts[context & ~CONTEXT_CUSTOM];
705 /* Create the lookup table with the scaling values for the reflections */
706 void init_reflect_table(void)
708 int i;
709 for (i = 0; i < REFLECT_HEIGHT; i++)
710 reflect_table[i] =
711 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
712 (5 * REFLECT_HEIGHT);
716 Create an index of all albums from the database.
717 Also store the album names so we can access them later.
719 int create_album_index(void)
721 buf_size -= UNIQBUF_SIZE * sizeof(long);
722 long *uniqbuf = (long *)(buf_size + (char *)buf);
723 album = ((struct album_data *)uniqbuf) - 1;
724 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
725 album_count = 0;
726 rb->tagcache_search(&tcs, tag_album);
727 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
728 unsigned int l, old_l = 0;
729 album_names = buf;
730 album[0].name_idx = 0;
731 while (rb->tagcache_get_next(&tcs))
733 buf_size -= sizeof(struct album_data);
734 l = tcs.result_len;
735 if ( album_count > 0 )
736 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
738 if ( l > buf_size )
739 /* not enough memory */
740 return ERROR_BUFFER_FULL;
742 rb->strcpy(buf, tcs.result);
743 buf_size -= l;
744 buf = l + (char *)buf;
745 album[-album_count].seek = tcs.result_seek;
746 old_l = l;
747 album_count++;
749 rb->tagcache_search_finish(&tcs);
750 ALIGN_BUFFER(buf, buf_size, 4);
751 int i;
752 struct album_data* tmp_album = (struct album_data*)buf;
753 for (i = album_count - 1; i >= 0; i--)
754 tmp_album[i] = album[-i];
755 album = tmp_album;
756 buf = album + album_count;
757 buf_size += UNIQBUF_SIZE * sizeof(long);
758 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
762 Return a pointer to the album name of the given slide_index
764 char* get_album_name(const int slide_index)
766 return album_names + album[slide_index].name_idx;
770 Return a pointer to the track name of the active album
771 create_track_index has to be called first.
773 char* get_track_name(const int track_index)
775 if ( track_index < track_count )
776 return track_names + tracks[track_index].name_idx;
777 return 0;
781 Compare two unsigned ints passed via pointers.
783 int compare_uints (const void *a_v, const void *b_v)
785 uint32_t a = *(uint32_t *)a_v;
786 uint32_t b = *(uint32_t *)b_v;
787 return (int)(a - b);
791 Create the track index of the given slide_index.
793 int create_track_index(const int slide_index)
795 if ( slide_index == track_index ) {
796 return -1;
799 if (!rb->tagcache_search(&tcs, tag_title))
800 return -1;
802 struct track_data temp_tracks[MAX_TRACKS];
803 uint32_t temp_tracknums[MAX_TRACKS];
805 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
806 track_count=0;
807 int string_index = 0, i, track_num;
809 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
811 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber);
812 int avail = sizeof(track_names) - string_index;
813 int len;
814 if (track_num >= 0)
816 len = 1 + rb->snprintf(track_names + string_index , avail,
817 "%d: %s", track_num, tcs.result);
819 else
821 track_num = 0;
822 len = tcs.result_len;
823 rb->strncpy(track_names + string_index, tcs.result, avail);
825 if (len > avail)
826 return -1;
827 temp_tracknums[track_count] = (track_num << 8) + track_count;
828 temp_tracks[track_count].name_idx = string_index;
829 temp_tracks[track_count].seek = tcs.result_seek;
830 track_count++;
831 string_index += len;
834 rb->tagcache_search_finish(&tcs);
835 track_index = slide_index;
837 /* now fix the track list order */
838 rb->qsort(temp_tracknums, track_count, sizeof(int), compare_uints);
839 for (i = 0; i < track_count; i++)
841 tracks[i].name_idx = temp_tracks[0xFF & temp_tracknums[i]].name_idx;
842 tracks[i].seek = temp_tracks[0xFF & temp_tracknums[i]].seek;
844 return (track_count > 0) ? 0 : -1;
848 Determine filename of the album art for the given slide_index and
849 store the result in buf.
850 The algorithm looks for the first track of the given album uses
851 find_albumart to find the filename.
853 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
854 int buflen)
856 if ( slide_index == -1 )
858 rb->strncpy( buf, EMPTY_SLIDE, buflen );
861 if (!rb->tagcache_search(&tcs, tag_filename))
862 return false;
864 bool result;
865 /* find the first track of the album */
866 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
868 if ( rb->tagcache_get_next(&tcs) ) {
869 struct mp3entry id3;
870 int fd;
872 fd = rb->open(tcs.result, O_RDONLY);
873 rb->get_metadata(&id3, fd, tcs.result);
874 rb->close(fd);
875 if ( search_albumart_files(&id3, "", buf, buflen) )
876 result = true;
877 else
878 result = false;
880 else {
881 /* did not find a matching track */
882 result = false;
884 rb->tagcache_search_finish(&tcs);
885 return result;
889 Draw the PictureFlow logo
891 void draw_splashscreen(void)
893 struct screen* display = rb->screens[0];
894 const struct picture* logo = &(logos[display->screen_type]);
896 #if LCD_DEPTH > 1
897 rb->lcd_set_background(N_BRIGHT(0));
898 rb->lcd_set_foreground(N_BRIGHT(255));
899 #else
900 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
901 #endif
902 rb->lcd_clear_display();
904 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
905 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
906 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
907 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
908 #else
909 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
910 #endif
912 rb->lcd_update();
917 Draw a simple progress bar
919 void draw_progressbar(int step)
921 int txt_w, txt_h;
922 const int bar_height = 22;
923 const int w = LCD_WIDTH - 20;
924 const int x = 10;
926 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
928 int y = (LCD_HEIGHT - txt_h)/2;
930 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
931 y += (txt_h + 5);
933 #if LCD_DEPTH > 1
934 rb->lcd_set_foreground(N_BRIGHT(100));
935 #endif
936 rb->lcd_drawrect(x, y, w+2, bar_height);
937 #if LCD_DEPTH > 1
938 rb->lcd_set_foreground(N_PIX(165, 231, 82));
939 #endif
941 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
942 #if LCD_DEPTH > 1
943 rb->lcd_set_foreground(N_BRIGHT(255));
944 #endif
945 rb->lcd_update();
946 rb->yield();
950 Precomupte the album art images and store them in CACHE_PREFIX.
952 bool create_albumart_cache(void)
954 int ret;
956 int i, slides = 0;
957 struct bitmap input_bmp;
959 char pfraw_file[MAX_PATH];
960 char albumart_file[MAX_PATH];
961 unsigned int format = FORMAT_NATIVE;
962 cache_version = 0;
963 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
964 if (resize)
965 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
966 for (i=0; i < album_count; i++)
968 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
970 /* delete existing cache, so it's a true rebuild */
971 if(rb->file_exists(pfraw_file))
972 rb->remove(pfraw_file);
973 draw_progressbar(i);
974 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
975 continue;
977 input_bmp.data = buf;
978 input_bmp.width = DISPLAY_WIDTH;
979 input_bmp.height = DISPLAY_HEIGHT;
980 ret = read_image_file(albumart_file, &input_bmp,
981 buf_size, format, &format_transposed);
982 if (ret <= 0) {
983 rb->splash(HZ, "Could not read bmp");
984 continue; /* skip missing/broken files */
986 if (!save_pfraw(pfraw_file, &input_bmp))
988 rb->splash(HZ, "Could not write bmp");
990 slides++;
991 if ( rb->button_get(false) == PF_MENU ) return false;
993 if ( slides == 0 ) {
994 /* Warn the user that we couldn't find any albumart */
995 rb->splash(2*HZ, "No album art found");
996 return false;
998 return true;
1002 Thread used for loading and preparing bitmaps in the background
1004 void thread(void)
1006 long sleep_time = 5 * HZ;
1007 struct queue_event ev;
1008 while (1) {
1009 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1010 switch (ev.id) {
1011 case EV_EXIT:
1012 return;
1013 case EV_WAKEUP:
1014 /* we just woke up */
1015 break;
1017 while ( load_new_slide() ) {
1018 rb->yield();
1019 switch (ev.id) {
1020 case EV_EXIT:
1021 return;
1029 End the thread by posting the EV_EXIT event
1031 void end_pf_thread(void)
1033 if ( thread_is_running ) {
1034 rb->queue_post(&thread_q, EV_EXIT, 0);
1035 rb->thread_wait(thread_id);
1036 /* remove the thread's queue from the broadcast list */
1037 rb->queue_delete(&thread_q);
1038 thread_is_running = false;
1045 Create the thread an setup the event queue
1047 bool create_pf_thread(void)
1049 /* put the thread's queue in the bcast list */
1050 rb->queue_init(&thread_q, true);
1051 if ((thread_id = rb->create_thread(
1052 thread,
1053 thread_stack,
1054 sizeof(thread_stack),
1056 "Picture load thread"
1057 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1058 PRIORITY_REALTIME + 1))
1059 IF_COP(, CPU)
1061 ) == 0) {
1062 return false;
1064 thread_is_running = true;
1065 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1066 return true;
1070 Safe the given bitmap as filename in the pfraw format
1072 bool save_pfraw(char* filename, struct bitmap *bm)
1074 struct pfraw_header bmph;
1075 bmph.width = bm->width;
1076 bmph.height = bm->height;
1077 int fh = rb->creat( filename );
1078 if( fh < 0 ) return false;
1079 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1080 int y;
1081 for( y = 0; y < bm->height; y++ )
1083 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1084 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1086 rb->close( fh );
1087 return true;
1092 * The following functions implement the linked-list-in-array used to manage
1093 * the LRU cache of slides, and the list of free cache slots.
1096 #define seek_right_while(start, cond) \
1097 ({ \
1098 int ind_, next_ = (start); \
1099 do { \
1100 ind_ = next_; \
1101 next_ = cache[ind_].next; \
1102 } while (next_ != cache_used && (cond)); \
1103 ind_; \
1106 #define seek_left_while(start, cond) \
1107 ({ \
1108 int ind_, next_ = (start); \
1109 do { \
1110 ind_ = next_; \
1111 next_ = cache[ind_].prev; \
1112 } while (ind_ != cache_used && (cond)); \
1113 ind_; \
1117 Pop the given item from the linked list starting at *head, returning the next
1118 item, or -1 if the list is now empty.
1120 static inline int lla_pop_item (int *head, int i)
1122 int prev = cache[i].prev;
1123 int next = cache[i].next;
1124 if (i == next)
1126 *head = -1;
1127 return -1;
1129 else if (i == *head)
1130 *head = next;
1131 cache[next].prev = prev;
1132 cache[prev].next = next;
1133 return next;
1138 Pop the head item from the list starting at *head, returning the index of the
1139 item, or -1 if the list is already empty.
1141 static inline int lla_pop_head (int *head)
1143 int i = *head;
1144 if (i != -1)
1145 lla_pop_item(head, i);
1146 return i;
1150 Insert the item at index i before the one at index p.
1152 static inline void lla_insert (int i, int p)
1154 int next = p;
1155 int prev = cache[next].prev;
1156 cache[next].prev = i;
1157 cache[prev].next = i;
1158 cache[i].next = next;
1159 cache[i].prev = prev;
1164 Insert the item at index i at the end of the list starting at *head.
1166 static inline void lla_insert_tail (int *head, int i)
1168 if (*head == -1)
1170 *head = i;
1171 cache[i].next = i;
1172 cache[i].prev = i;
1173 } else
1174 lla_insert(i, *head);
1178 Insert the item at index i before the one at index p.
1180 static inline void lla_insert_after(int i, int p)
1182 p = cache[p].next;
1183 lla_insert(i, p);
1188 Insert the item at index i before the one at index p in the list starting at
1189 *head
1191 static inline void lla_insert_before(int *head, int i, int p)
1193 lla_insert(i, p);
1194 if (*head == p)
1195 *head = i;
1200 Free the used slide at index i, and its buffer, and move it to the free
1201 slides list.
1203 static inline void free_slide(int i)
1205 if (cache[i].hid != empty_slide_hid)
1206 buflib_free(&buf_ctx, cache[i].hid);
1207 cache[i].index = -1;
1208 lla_pop_item(&cache_used, i);
1209 lla_insert_tail(&cache_free, i);
1210 if (cache_used == -1)
1212 cache_right_index = -1;
1213 cache_left_index = -1;
1214 cache_center_index = -1;
1220 Free one slide ranked above the given priority. If no such slide can be found,
1221 return false.
1223 static inline int free_slide_prio(int prio)
1225 if (cache_used == -1)
1226 return false;
1227 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1228 int prio_l = cache[l].index < center_index ?
1229 center_index - cache[l].index : 0;
1230 int prio_r = cache[r].index > center_index ?
1231 cache[r].index - center_index : 0;
1232 if (prio_l > prio_r)
1234 i = l;
1235 prio_max = prio_l;
1236 } else {
1237 i = r;
1238 prio_max = prio_r;
1240 if (prio_max > prio)
1242 if (i == cache_left_index)
1243 cache_left_index = cache[i].next;
1244 if (i == cache_right_index)
1245 cache_right_index = cache[i].prev;
1246 free_slide(i);
1247 return true;
1248 } else
1249 return false;
1253 Read the pfraw image given as filename and return the hid of the buffer
1255 int read_pfraw(char* filename, int prio)
1257 struct pfraw_header bmph;
1258 int fh = rb->open(filename, O_RDONLY);
1259 if( fh < 0 )
1260 return empty_slide_hid;
1261 else
1262 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1264 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1265 bmph.width * bmph.height;
1267 int hid;
1268 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1270 if (!hid) {
1271 rb->close( fh );
1272 return 0;
1275 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1277 bm->width = bmph.width;
1278 bm->height = bmph.height;
1279 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1281 int y;
1282 for( y = 0; y < bm->height; y++ )
1284 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1285 data += bm->width;
1287 rb->close( fh );
1288 return hid;
1293 Load the surface for the given slide_index into the cache at cache_index.
1295 static inline bool load_and_prepare_surface(const int slide_index,
1296 const int cache_index,
1297 const int prio)
1299 char tmp_path_name[MAX_PATH+1];
1300 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1301 slide_index);
1303 int hid = read_pfraw(tmp_path_name, prio);
1304 if (!hid)
1305 return false;
1307 cache[cache_index].hid = hid;
1309 if ( cache_index < SLIDE_CACHE_SIZE ) {
1310 cache[cache_index].index = slide_index;
1313 return true;
1318 Load the "next" slide that we can load, freeing old slides if needed, provided
1319 that they are further from center_index than the current slide
1321 bool load_new_slide(void)
1323 int i = -1;
1324 if (cache_center_index != -1)
1326 int next, prev;
1327 if (cache[cache_center_index].index != center_index)
1329 if (cache[cache_center_index].index < center_index)
1331 cache_center_index = seek_right_while(cache_center_index,
1332 cache[next_].index <= center_index);
1333 prev = cache_center_index;
1334 next = cache[cache_center_index].next;
1336 else
1338 cache_center_index = seek_left_while(cache_center_index,
1339 cache[next_].index >= center_index);
1340 next = cache_center_index;
1341 prev = cache[cache_center_index].prev;
1343 if (cache[cache_center_index].index != center_index)
1345 if (cache_free == -1)
1346 free_slide_prio(0);
1347 i = lla_pop_head(&cache_free);
1348 if (!load_and_prepare_surface(center_index, i, 0))
1349 goto fail_and_refree;
1350 if (cache[next].index == -1)
1352 if (cache[prev].index == -1)
1353 goto insert_first_slide;
1354 else
1355 next = cache[prev].next;
1357 lla_insert(i, next);
1358 if (cache[i].index < cache[cache_used].index)
1359 cache_used = i;
1360 cache_center_index = i;
1361 cache_left_index = i;
1362 cache_right_index = i;
1363 return true;
1366 if (cache[cache_left_index].index >
1367 cache[cache_center_index].index)
1368 cache_left_index = cache_center_index;
1369 if (cache[cache_right_index].index <
1370 cache[cache_center_index].index)
1371 cache_right_index = cache_center_index;
1372 cache_left_index = seek_left_while(cache_left_index,
1373 cache[ind_].index - 1 == cache[next_].index);
1374 cache_right_index = seek_right_while(cache_right_index,
1375 cache[ind_].index - 1 == cache[next_].index);
1376 int prio_l = cache[cache_center_index].index -
1377 cache[cache_left_index].index + 1;
1378 int prio_r = cache[cache_right_index].index -
1379 cache[cache_center_index].index + 1;
1380 if ((prio_l < prio_r ||
1381 cache[cache_right_index].index >= number_of_slides) &&
1382 cache[cache_left_index].index > 0)
1384 if (cache_free == -1 && !free_slide_prio(prio_l))
1385 return false;
1386 i = lla_pop_head(&cache_free);
1387 if (load_and_prepare_surface(cache[cache_left_index].index
1388 - 1, i, prio_l))
1390 lla_insert_before(&cache_used, i, cache_left_index);
1391 cache_left_index = i;
1392 return true;
1394 } else if(cache[cache_right_index].index < number_of_slides - 1)
1396 if (cache_free == -1 && !free_slide_prio(prio_r))
1397 return false;
1398 i = lla_pop_head(&cache_free);
1399 if (load_and_prepare_surface(cache[cache_right_index].index
1400 + 1, i, prio_r))
1402 lla_insert_after(i, cache_right_index);
1403 cache_right_index = i;
1404 return true;
1407 } else {
1408 i = lla_pop_head(&cache_free);
1409 if (load_and_prepare_surface(center_index, i, 0))
1411 insert_first_slide:
1412 cache[i].next = i;
1413 cache[i].prev = i;
1414 cache_center_index = i;
1415 cache_left_index = i;
1416 cache_right_index = i;
1417 cache_used = i;
1418 return true;
1421 fail_and_refree:
1422 if (i != -1)
1424 lla_insert_tail(&cache_free, i);
1426 return false;
1431 Get a slide from the buffer
1433 static inline struct dim *get_slide(const int hid)
1435 if (!hid)
1436 return NULL;
1438 struct dim *bmp;
1440 bmp = buflib_get_data(&buf_ctx, hid);
1442 return bmp;
1447 Return the requested surface
1449 static inline struct dim *surface(const int slide_index)
1451 if (slide_index < 0)
1452 return 0;
1453 if (slide_index >= number_of_slides)
1454 return 0;
1455 int i;
1456 if ((i = cache_used ) != -1)
1458 do {
1459 if (cache[i].index == slide_index)
1460 return get_slide(cache[i].hid);
1461 i = cache[i].next;
1462 } while (i != cache_used);
1464 return get_slide(empty_slide_hid);
1468 adjust slides so that they are in "steady state" position
1470 void reset_slides(void)
1472 center_slide.angle = 0;
1473 center_slide.cx = 0;
1474 center_slide.cy = 0;
1475 center_slide.distance = 0;
1476 center_slide.slide_index = center_index;
1478 int i;
1479 for (i = 0; i < num_slides; i++) {
1480 struct slide_data *si = &left_slides[i];
1481 si->angle = itilt;
1482 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1483 si->cy = offsetY;
1484 si->slide_index = center_index - 1 - i;
1485 si->distance = 0;
1488 for (i = 0; i < num_slides; i++) {
1489 struct slide_data *si = &right_slides[i];
1490 si->angle = -itilt;
1491 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1492 si->cy = offsetY;
1493 si->slide_index = center_index + 1 + i;
1494 si->distance = 0;
1500 Updates look-up table and other stuff necessary for the rendering.
1501 Call this when the viewport size or slide dimension is changed.
1503 * To calculate the offset that will provide the proper margin, we use the same
1504 * projection used to render the slides. The solution for xc, the slide center,
1505 * is:
1506 * xp * (zo + xs * sin(r))
1507 * xc = xp - xs * cos(r) + ───────────────────────
1509 * TODO: support moving the side slides toward or away from the camera
1511 void recalc_offsets(void)
1513 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1514 PFreal zo;
1515 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1516 PFREAL_ONE) * zoom / 100;
1517 PFreal cosr, sinr;
1519 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1520 cosr = fcos(-itilt);
1521 sinr = fsin(-itilt);
1522 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1523 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1524 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1525 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1526 / CAM_DIST;
1527 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1532 Fade the given color by spreading the fb_data (ushort)
1533 to an uint, multiply and compress the result back to a ushort.
1535 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1536 static inline unsigned fade_color(pix_t c, unsigned a)
1538 unsigned int result;
1539 c = swap16(c);
1540 a = (a + 2) & 0x1fc;
1541 result = ((c & 0xf81f) * a) & 0xf81f00;
1542 result |= ((c & 0x7e0) * a) & 0x7e000;
1543 result >>= 8;
1544 return swap16(result);
1546 #elif LCD_PIXELFORMAT == RGB565
1547 static inline unsigned fade_color(pix_t c, unsigned a)
1549 unsigned int result;
1550 a = (a + 2) & 0x1fc;
1551 result = ((c & 0xf81f) * a) & 0xf81f00;
1552 result |= ((c & 0x7e0) * a) & 0x7e000;
1553 result >>= 8;
1554 return result;
1556 #else
1557 static inline unsigned fade_color(pix_t c, unsigned a)
1559 unsigned val = c;
1560 return MULUQ(val, a) >> 8;
1562 #endif
1565 * Render a single slide
1566 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1567 * on the slide from its center, zo is the slide's depth offset from the plane
1568 * of the display, r is the angle at which the slide is tilted, and xp is the
1569 * point on the display corresponding to xs on the slide, the projection
1570 * formulas are:
1572 * z * (xc + xs * cos(r))
1573 * xp = ──────────────────────
1574 * z + zo + xs * sin(r)
1576 * z * (xc - xp) - xp * zo
1577 * xs = ────────────────────────
1578 * xp * sin(r) - z * cos(r)
1580 * We use the xp projection once, to find the left edge of the slide on the
1581 * display. From there, we use the xs reverse projection to find the horizontal
1582 * offset from the slide center of each column on the screen, until we reach
1583 * the right edge of the slide, or the screen. The reverse projection can be
1584 * optimized by saving the numerator and denominator of the fraction, which can
1585 * then be incremented by (z + zo) and sin(r) respectively.
1587 void render_slide(struct slide_data *slide, const int alpha)
1589 struct dim *bmp = surface(slide->slide_index);
1590 if (!bmp) {
1591 return;
1593 if (slide->angle > 255 || slide->angle < -255)
1594 return;
1595 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1597 const int sw = bmp->width;
1598 const int sh = bmp->height;
1599 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1600 const int w = LCD_WIDTH;
1602 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1604 if (alpha == 256) { /* opaque -> copy table */
1605 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1606 } else { /* precalculate faded table */
1607 int i, lalpha;
1608 for (i = 0; i < REFLECT_HEIGHT; i++) {
1609 lalpha = reflect_table[i];
1610 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1614 PFreal cosr = fcos(slide->angle);
1615 PFreal sinr = fsin(slide->angle);
1616 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1617 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1618 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1619 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1620 (CAM_DIST_R + zo + fmul(xs,sinr)));
1622 /* Since we're finding the screen position of the left edge of the slide,
1623 * we round up.
1625 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1626 >> PFREAL_SHIFT;
1627 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1628 if (xi >= w) {
1629 return;
1631 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1632 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1633 xs = fdiv(xsnum, xsden);
1635 xsnumi = -CAM_DIST_R - zo;
1636 xsdeni = sinr;
1637 int x;
1638 int dy = PFREAL_ONE;
1639 for (x = xi; x < w; x++) {
1640 int column = (xs - slide_left) / PFREAL_ONE;
1641 if (column >= sw)
1642 break;
1643 if (zo || slide->angle)
1644 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1646 const pix_t *ptr = &src[column * bmp->height];
1647 const int pixelstep = BUFFER_WIDTH;
1649 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1650 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1651 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1653 if (alpha == 256) {
1654 while (p >= plim) {
1655 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1656 p -= dy;
1657 pixel -= pixelstep;
1659 } else {
1660 while (p >= plim) {
1661 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1662 p -= dy;
1663 pixel -= pixelstep;
1666 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1667 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1668 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1669 p + (LCD_HEIGHT/2) * dy);
1670 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1672 if (alpha == 256) {
1673 while (p < plim) {
1674 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1675 p += dy;
1676 pixel += pixelstep;
1678 } else {
1679 while (p < plim) {
1680 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1681 p += dy;
1682 pixel += pixelstep;
1685 while (p < plim2) {
1686 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1687 int lalpha = reftab[ty];
1688 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1689 p += dy;
1690 pixel += pixelstep;
1693 if (zo || slide->angle)
1695 xsnum += xsnumi;
1696 xsden += xsdeni;
1697 xs = fdiv(xsnum, xsden);
1698 } else
1699 xs += PFREAL_ONE;
1702 /* let the music play... */
1703 rb->yield();
1704 return;
1709 Jump the the given slide_index
1711 static inline void set_current_slide(const int slide_index)
1713 int old_center_index = center_index;
1714 step = 0;
1715 center_index = fbound(slide_index, 0, number_of_slides - 1);
1716 if (old_center_index != center_index)
1717 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1718 target = center_index;
1719 slide_frame = slide_index << 16;
1720 reset_slides();
1724 Start the animation for changing slides
1726 void start_animation(void)
1728 step = (target < center_slide.slide_index) ? -1 : 1;
1729 pf_state = pf_scrolling;
1733 Go to the previous slide
1735 void show_previous_slide(void)
1737 if (step == 0) {
1738 if (center_index > 0) {
1739 target = center_index - 1;
1740 start_animation();
1742 } else if ( step > 0 ) {
1743 target = center_index;
1744 start_animation();
1745 } else {
1746 target = fmax(0, center_index - 2);
1752 Go to the next slide
1754 void show_next_slide(void)
1756 if (step == 0) {
1757 if (center_index < number_of_slides - 1) {
1758 target = center_index + 1;
1759 start_animation();
1761 } else if ( step < 0 ) {
1762 target = center_index;
1763 start_animation();
1764 } else {
1765 target = fmin(center_index + 2, number_of_slides - 1);
1771 Render the slides. Updates only the offscreen buffer.
1773 void render_all_slides(void)
1775 MYLCD(set_background)(G_BRIGHT(0));
1776 /* TODO: Optimizes this by e.g. invalidating rects */
1777 MYLCD(clear_display)();
1779 int nleft = num_slides;
1780 int nright = num_slides;
1782 int index;
1783 if (step == 0) {
1784 /* no animation, boring plain rendering */
1785 for (index = nleft - 2; index >= 0; index--) {
1786 int alpha = (index < nleft - 2) ? 256 : 128;
1787 alpha -= extra_fade;
1788 if (alpha > 0 )
1789 render_slide(&left_slides[index], alpha);
1791 for (index = nright - 2; index >= 0; index--) {
1792 int alpha = (index < nright - 2) ? 256 : 128;
1793 alpha -= extra_fade;
1794 if (alpha > 0 )
1795 render_slide(&right_slides[index], alpha);
1797 } else {
1798 /* the first and last slide must fade in/fade out */
1799 for (index = nleft - 1; index >= 0; index--) {
1800 int alpha = 256;
1801 if (index == nleft - 1)
1802 alpha = (step > 0) ? 0 : 128 - fade / 2;
1803 if (index == nleft - 2)
1804 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1805 if (index == nleft - 3)
1806 alpha = (step > 0) ? 256 - fade / 2 : 256;
1807 render_slide(&left_slides[index], alpha);
1809 for (index = nright - 1; index >= 0; index--) {
1810 int alpha = (index < nright - 2) ? 256 : 128;
1811 if (index == nright - 1)
1812 alpha = (step > 0) ? fade / 2 : 0;
1813 if (index == nright - 2)
1814 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1815 if (index == nright - 3)
1816 alpha = (step > 0) ? 256 : 128 + fade / 2;
1817 render_slide(&right_slides[index], alpha);
1820 render_slide(&center_slide, 256);
1825 Updates the animation effect. Call this periodically from a timer.
1827 void update_scroll_animation(void)
1829 if (step == 0)
1830 return;
1832 int speed = 16384;
1833 int i;
1835 /* deaccelerate when approaching the target */
1836 if (true) {
1837 const int max = 2 * 65536;
1839 int fi = slide_frame;
1840 fi -= (target << 16);
1841 if (fi < 0)
1842 fi = -fi;
1843 fi = fmin(fi, max);
1845 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1846 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1849 slide_frame += speed * step;
1851 int index = slide_frame >> 16;
1852 int pos = slide_frame & 0xffff;
1853 int neg = 65536 - pos;
1854 int tick = (step < 0) ? neg : pos;
1855 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1857 /* the leftmost and rightmost slide must fade away */
1858 fade = pos / 256;
1860 if (step < 0)
1861 index++;
1862 if (center_index != index) {
1863 center_index = index;
1864 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1865 slide_frame = index << 16;
1866 center_slide.slide_index = center_index;
1867 for (i = 0; i < num_slides; i++)
1868 left_slides[i].slide_index = center_index - 1 - i;
1869 for (i = 0; i < num_slides; i++)
1870 right_slides[i].slide_index = center_index + 1 + i;
1873 center_slide.angle = (step * tick * itilt) >> 16;
1874 center_slide.cx = -step * fmul(offsetX, ftick);
1875 center_slide.cy = fmul(offsetY, ftick);
1877 if (center_index == target) {
1878 reset_slides();
1879 pf_state = pf_idle;
1880 step = 0;
1881 fade = 256;
1882 return;
1885 for (i = 0; i < num_slides; i++) {
1886 struct slide_data *si = &left_slides[i];
1887 si->angle = itilt;
1888 si->cx =
1889 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1890 * slide_spacing * ftick);
1891 si->cy = offsetY;
1894 for (i = 0; i < num_slides; i++) {
1895 struct slide_data *si = &right_slides[i];
1896 si->angle = -itilt;
1897 si->cx =
1898 offsetX + slide_spacing * i * PFREAL_ONE - step
1899 * slide_spacing * ftick;
1900 si->cy = offsetY;
1903 if (step > 0) {
1904 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1905 right_slides[0].angle = -(neg * itilt) >> 16;
1906 right_slides[0].cx = fmul(offsetX, ftick);
1907 right_slides[0].cy = fmul(offsetY, ftick);
1908 } else {
1909 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1910 left_slides[0].angle = (pos * itilt) >> 16;
1911 left_slides[0].cx = -fmul(offsetX, ftick);
1912 left_slides[0].cy = fmul(offsetY, ftick);
1915 /* must change direction ? */
1916 if (target < index)
1917 if (step > 0)
1918 step = -1;
1919 if (target > index)
1920 if (step < 0)
1921 step = 1;
1926 Cleanup the plugin
1928 void cleanup(void *parameter)
1930 (void) parameter;
1931 /* Turn on backlight timeout (revert to settings) */
1932 backlight_use_settings(); /* backlight control in lib/helper.c */
1934 #ifdef USEGSLIB
1935 grey_release();
1936 #endif
1940 Create the "?" slide, that is shown while loading
1941 or when no cover was found.
1943 int create_empty_slide(bool force)
1945 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1946 struct bitmap input_bmp;
1947 int ret;
1948 input_bmp.width = DISPLAY_WIDTH;
1949 input_bmp.height = DISPLAY_HEIGHT;
1950 #if LCD_DEPTH > 1
1951 input_bmp.format = FORMAT_NATIVE;
1952 #endif
1953 input_bmp.data = (char*)buf;
1954 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1955 buf_size,
1956 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1957 &format_transposed);
1958 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1959 return false;
1962 return true;
1966 Shows the album name setting menu
1968 int album_name_menu(void)
1970 int selection = show_album_name;
1972 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
1973 "Hide album title", "Show at the bottom", "Show at the top");
1974 rb->do_menu(&album_name_menu, &selection, NULL, false);
1976 show_album_name = selection;
1977 return GO_TO_PREVIOUS;
1981 Shows the settings menu
1983 int settings_menu(void)
1985 int selection = 0;
1986 bool old_val;
1988 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1989 "Spacing", "Centre margin", "Number of slides", "Zoom",
1990 "Show album title", "Resize Covers", "Rebuild cache");
1992 do {
1993 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1994 switch(selection) {
1995 case 0:
1996 rb->set_bool("Show FPS", &show_fps);
1997 reset_track_list();
1998 break;
2000 case 1:
2001 rb->set_int("Spacing between slides", "", 1,
2002 &slide_spacing,
2003 NULL, 1, 0, 100, NULL );
2004 recalc_offsets();
2005 reset_slides();
2006 break;
2008 case 2:
2009 rb->set_int("Centre margin", "", 1,
2010 &center_margin,
2011 NULL, 1, 0, 80, NULL );
2012 recalc_offsets();
2013 reset_slides();
2014 break;
2016 case 3:
2017 rb->set_int("Number of slides", "", 1, &num_slides,
2018 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2019 recalc_offsets();
2020 reset_slides();
2021 break;
2023 case 4:
2024 rb->set_int("Zoom", "", 1, &zoom,
2025 NULL, 1, 10, 300, NULL );
2026 recalc_offsets();
2027 reset_slides();
2028 break;
2029 case 5:
2030 album_name_menu();
2031 reset_track_list();
2032 recalc_offsets();
2033 reset_slides();
2034 break;
2035 case 6:
2036 old_val = resize;
2037 rb->set_bool("Resize Covers", &resize);
2038 if (old_val == resize) /* changed? */
2039 break;
2040 /* fallthrough if changed, since cache needs to be rebuilt */
2041 case 7:
2042 cache_version = 0;
2043 rb->remove(EMPTY_SLIDE);
2044 rb->splash(HZ, "Cache will be rebuilt on next restart");
2045 break;
2047 case MENU_ATTACHED_USB:
2048 return PLUGIN_USB_CONNECTED;
2050 } while ( selection >= 0 );
2051 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2052 return 0;
2056 Show the main menu
2058 int main_menu(void)
2060 int selection = 0;
2061 int result;
2063 #if LCD_DEPTH > 1
2064 rb->lcd_set_foreground(N_BRIGHT(255));
2065 #endif
2067 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2068 "Settings", "Return", "Quit");
2069 while (1) {
2070 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2071 case 0:
2072 result = settings_menu();
2073 if ( result != 0 ) return result;
2074 break;
2076 case 1:
2077 return 0;
2079 case 2:
2080 return -1;
2082 case MENU_ATTACHED_USB:
2083 return PLUGIN_USB_CONNECTED;
2085 default:
2086 return 0;
2092 Animation step for zooming into the current cover
2094 void update_cover_in_animation(void)
2096 cover_animation_keyframe++;
2097 if( cover_animation_keyframe < 20 ) {
2098 center_slide.distance-=5;
2099 center_slide.angle+=1;
2100 extra_fade += 13;
2102 else if( cover_animation_keyframe < 35 ) {
2103 center_slide.angle+=16;
2105 else {
2106 cover_animation_keyframe = 0;
2107 pf_state = pf_show_tracks;
2112 Animation step for zooming out the current cover
2114 void update_cover_out_animation(void)
2116 cover_animation_keyframe++;
2117 if( cover_animation_keyframe <= 15 ) {
2118 center_slide.angle-=16;
2120 else if( cover_animation_keyframe < 35 ) {
2121 center_slide.distance+=5;
2122 center_slide.angle-=1;
2123 extra_fade -= 13;
2125 else {
2126 cover_animation_keyframe = 0;
2127 pf_state = pf_idle;
2132 Draw a blue gradient at y with height h
2134 static inline void draw_gradient(int y, int h)
2136 static int r, inc, c;
2137 inc = (100 << 8) / h;
2138 c = 0;
2139 selected_track_pulse = (selected_track_pulse+1) % 10;
2140 int c2 = selected_track_pulse - 5;
2141 for (r=0; r<h; r++) {
2142 #ifdef HAVE_LCD_COLOR
2143 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2144 c2+250-(c >> 8)));
2145 #else
2146 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2147 #endif
2148 MYLCD(hline)(0, LCD_WIDTH, r+y);
2149 if ( r > h/2 )
2150 c-=inc;
2151 else
2152 c+=inc;
2157 static void track_list_yh(int char_height)
2159 switch (show_album_name)
2161 case album_name_hide:
2162 track_list_y = (show_fps ? char_height : 0);
2163 track_list_h = LCD_HEIGHT - track_list_y;
2164 break;
2165 case album_name_bottom:
2166 track_list_y = (show_fps ? char_height : 0);
2167 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2168 break;
2169 default: /* case album_name_top */
2170 track_list_y = char_height * 2;
2171 track_list_h = LCD_HEIGHT - track_list_y -
2172 (show_fps ? char_height : 0);
2173 break;
2178 Reset the track list after a album change
2180 void reset_track_list(void)
2182 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2183 track_list_yh(albumtxt_h);
2184 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2185 start_index_track_list = 0;
2186 track_scroll_index = 0;
2187 track_scroll_dir = 1;
2188 selected_track = 0;
2190 /* let the tracklist start more centered
2191 * if the screen isn't filled with tracks */
2192 if (track_count*albumtxt_h < track_list_h)
2194 track_list_h = track_count * albumtxt_h;
2195 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2200 Display the list of tracks
2202 void show_track_list(void)
2204 MYLCD(clear_display)();
2205 if ( center_slide.slide_index != track_index ) {
2206 create_track_index(center_slide.slide_index);
2207 reset_track_list();
2209 static int titletxt_w, titletxt_x, color, titletxt_h;
2210 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2212 int titletxt_y = track_list_y;
2213 int track_i;
2214 track_i = start_index_track_list;
2215 for (;track_i < track_list_visible_entries+start_index_track_list;
2216 track_i++)
2218 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2219 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2220 if ( track_i == selected_track ) {
2221 draw_gradient(titletxt_y, titletxt_h);
2222 MYLCD(set_foreground)(G_BRIGHT(255));
2223 if (titletxt_w > LCD_WIDTH ) {
2224 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2225 track_scroll_dir = 1;
2226 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2227 track_scroll_index += track_scroll_dir*2;
2228 titletxt_x = track_scroll_index;
2230 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2232 else {
2233 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2234 MYLCD(set_foreground)(G_BRIGHT(color));
2235 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2237 titletxt_y += titletxt_h;
2241 void select_next_track(void)
2243 if ( selected_track < track_count - 1 ) {
2244 selected_track++;
2245 track_scroll_index = 0;
2246 track_scroll_dir = 1;
2247 if (selected_track==(track_list_visible_entries+start_index_track_list))
2248 start_index_track_list++;
2252 void select_prev_track(void)
2254 if (selected_track > 0 ) {
2255 if (selected_track==start_index_track_list) start_index_track_list--;
2256 track_scroll_index = 0;
2257 track_scroll_dir = 1;
2258 selected_track--;
2263 Draw the current album name
2265 void draw_album_text(void)
2267 if (0 == show_album_name)
2268 return;
2270 int albumtxt_w, albumtxt_h;
2271 int albumtxt_y = 0;
2273 char *albumtxt;
2274 int c;
2275 /* Draw album text */
2276 if ( pf_state == pf_scrolling ) {
2277 c = ((slide_frame & 0xffff )/ 255);
2278 if (step < 0) c = 255-c;
2279 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2280 albumtxt = get_album_name(center_index+step);
2281 c = (c-128)*2;
2283 else {
2284 albumtxt = get_album_name(center_index);
2285 c = (128-c)*2;
2288 else {
2289 c= 255;
2290 albumtxt = get_album_name(center_index);
2293 MYLCD(set_foreground)(G_BRIGHT(c));
2294 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2295 if (center_index != prev_center_index) {
2296 albumtxt_x = 0;
2297 albumtxt_dir = -1;
2298 prev_center_index = center_index;
2301 if (show_album_name == album_name_top)
2302 albumtxt_y = albumtxt_h / 2;
2303 else
2304 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2306 if (albumtxt_w > LCD_WIDTH ) {
2307 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2308 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2309 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2310 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2311 albumtxt_x += albumtxt_dir;
2314 else {
2315 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2323 Main function that also contain the main plasma
2324 algorithm.
2326 int main(void)
2328 int ret;
2330 rb->lcd_setfont(FONT_UI);
2331 draw_splashscreen();
2333 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2334 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2335 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2336 return PLUGIN_ERROR;
2340 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2342 init_reflect_table();
2344 ALIGN_BUFFER(buf, buf_size, 4);
2345 ret = create_album_index();
2346 if (ret == ERROR_BUFFER_FULL) {
2347 rb->splash(HZ, "Not enough memory for album names");
2348 return PLUGIN_ERROR;
2349 } else if (ret == ERROR_NO_ALBUMS) {
2350 rb->splash(HZ, "No albums found. Please enable database");
2351 return PLUGIN_ERROR;
2354 ALIGN_BUFFER(buf, buf_size, 4);
2355 number_of_slides = album_count;
2356 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2357 rb->splash(HZ, "Could not create album art cache");
2358 return PLUGIN_ERROR;
2361 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2362 rb->splash(HZ, "Could not load the empty slide");
2363 return PLUGIN_ERROR;
2365 cache_version = CACHE_VERSION;
2366 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2369 #ifdef USEGSLIB
2370 long grey_buf_used;
2371 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2372 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2374 rb->splash(HZ, "Greylib init failed!");
2375 return PLUGIN_ERROR;
2377 grey_setfont(FONT_UI);
2378 buf_size -= grey_buf_used;
2379 buf = (void*)(grey_buf_used + (char*)buf);
2380 #endif
2381 buflib_init(&buf_ctx, (void *)buf, buf_size);
2383 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2385 rb->splash(HZ, "Unable to load empty slide image");
2386 return PLUGIN_ERROR;
2389 if (!create_pf_thread()) {
2390 rb->splash(HZ, "Cannot create thread!");
2391 return PLUGIN_ERROR;
2394 int i;
2396 /* initialize */
2397 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2398 cache[i].hid = 0;
2399 cache[i].index = 0;
2400 cache[i].next = i + 1;
2401 cache[i].prev = i - 1;
2403 cache[0].prev = i - 1;
2404 cache[i - 1].next = 0;
2405 cache_free = 0;
2406 buffer = LCD_BUF;
2408 pf_state = pf_idle;
2410 track_index = -1;
2411 extra_fade = 0;
2412 slide_frame = 0;
2413 step = 0;
2414 target = 0;
2415 fade = 256;
2417 recalc_offsets();
2418 reset_slides();
2420 char fpstxt[10];
2421 int button;
2423 int frames = 0;
2424 long last_update = *rb->current_tick;
2425 long current_update;
2426 long update_interval = 100;
2427 int fps = 0;
2428 int fpstxt_y;
2430 bool instant_update;
2431 #ifdef USEGSLIB
2432 grey_show(true);
2433 grey_set_drawmode(DRMODE_FG);
2434 #endif
2435 rb->lcd_set_drawmode(DRMODE_FG);
2436 while (true) {
2437 current_update = *rb->current_tick;
2438 frames++;
2440 /* Initial rendering */
2441 instant_update = false;
2443 /* Handle states */
2444 switch ( pf_state ) {
2445 case pf_scrolling:
2446 update_scroll_animation();
2447 render_all_slides();
2448 instant_update = true;
2449 break;
2450 case pf_cover_in:
2451 update_cover_in_animation();
2452 render_all_slides();
2453 instant_update = true;
2454 break;
2455 case pf_cover_out:
2456 update_cover_out_animation();
2457 render_all_slides();
2458 instant_update = true;
2459 break;
2460 case pf_show_tracks:
2461 show_track_list();
2462 break;
2463 case pf_idle:
2464 render_all_slides();
2465 break;
2468 /* Calculate FPS */
2469 if (current_update - last_update > update_interval) {
2470 fps = frames * HZ / (current_update - last_update);
2471 last_update = current_update;
2472 frames = 0;
2474 /* Draw FPS */
2475 if (show_fps)
2477 #ifdef USEGSLIB
2478 MYLCD(set_foreground)(G_BRIGHT(255));
2479 #else
2480 MYLCD(set_foreground)(G_PIX(255,0,0));
2481 #endif
2482 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2483 if (show_album_name == album_name_top)
2484 fpstxt_y = LCD_HEIGHT -
2485 rb->screens[SCREEN_MAIN]->getcharheight();
2486 else
2487 fpstxt_y = 0;
2488 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2490 draw_album_text();
2493 /* Copy offscreen buffer to LCD and give time to other threads */
2494 MYLCD(update)();
2495 rb->yield();
2497 /*/ Handle buttons */
2498 button = rb->get_custom_action(CONTEXT_CUSTOM|
2499 (pf_state == pf_show_tracks ? 1 : 0),
2500 instant_update ? 0 : HZ/16,
2501 get_context_map);
2503 switch (button) {
2504 case PF_QUIT:
2505 return PLUGIN_OK;
2507 case PF_BACK:
2508 if ( pf_state == pf_show_tracks )
2509 pf_state = pf_cover_out;
2510 if (pf_state == pf_idle || pf_state == pf_scrolling)
2511 return PLUGIN_OK;
2512 break;
2514 case PF_MENU:
2515 #ifdef USEGSLIB
2516 grey_show(false);
2517 #endif
2518 ret = main_menu();
2519 if ( ret == -1 ) return PLUGIN_OK;
2520 if ( ret != 0 ) return i;
2521 #ifdef USEGSLIB
2522 grey_show(true);
2523 #endif
2524 MYLCD(set_drawmode)(DRMODE_FG);
2525 break;
2527 case PF_NEXT:
2528 case PF_NEXT_REPEAT:
2529 if ( pf_state == pf_show_tracks )
2530 select_next_track();
2531 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2532 show_next_slide();
2533 break;
2535 case PF_PREV:
2536 case PF_PREV_REPEAT:
2537 if ( pf_state == pf_show_tracks )
2538 select_prev_track();
2539 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2540 show_previous_slide();
2541 break;
2543 case PF_SELECT:
2544 if ( pf_state == pf_idle ) {
2545 pf_state = pf_cover_in;
2547 break;
2549 default:
2550 if (rb->default_event_handler_ex(button, cleanup, NULL)
2551 == SYS_USB_CONNECTED)
2552 return PLUGIN_USB_CONNECTED;
2553 break;
2560 /*************************** Plugin entry point ****************************/
2562 enum plugin_status plugin_start(const void *parameter)
2564 int ret;
2565 (void) parameter;
2566 #if LCD_DEPTH > 1
2567 rb->lcd_set_backdrop(NULL);
2568 #endif
2569 /* Turn off backlight timeout */
2570 backlight_force_on(); /* backlight control in lib/helper.c */
2571 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2572 rb->cpu_boost(true);
2573 #endif
2574 #if PLUGIN_BUFFER_SIZE > 0x10000
2575 buf = rb->plugin_get_buffer(&buf_size);
2576 #else
2577 buf = rb->plugin_get_audio_buffer(&buf_size);
2578 #endif
2579 ret = main();
2580 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2581 rb->cpu_boost(false);
2582 #endif
2583 if ( ret == PLUGIN_OK ) {
2584 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2585 CONFIG_VERSION))
2587 rb->splash(HZ, "Error writing config.");
2588 ret = PLUGIN_ERROR;
2592 end_pf_thread();
2593 cleanup(NULL);
2594 return ret;