Make JPEG and BMP scaler optional with HAVE_JPEG and HAVE_BMP_SCALING, both defined...
[maemo-rb.git] / apps / plugins / pictureflow.c
blobb78b953f131e1d874b5e5973a388c227e0e85044
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_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 uint32_t *qp = (uint32_t*)row_in;
617 for (; dest < end; dest += ctx->bm->height)
618 *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor);
619 #else
620 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
621 uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31),
622 rb_rnd = SCALE_VAL(ctx->round, 31),
623 g_mul = SCALE_VAL(ctx->divisor, 63),
624 g_rnd = SCALE_VAL(ctx->round, 63);
625 int r, g, b;
626 for (; dest < end; dest += ctx->bm->height)
628 r = SC_MUL(qp->r + rb_rnd, rb_mul);
629 g = SC_MUL(qp->g + g_rnd, g_mul);
630 b = SC_MUL(qp->b + rb_rnd, rb_mul);
631 qp++;
632 *dest = LCD_RGBPACK_LCD(r,g,b);
634 #endif
637 static unsigned int get_size(struct bitmap *bm)
639 return bm->width * bm->height * sizeof(pix_t);
642 const struct custom_format format_transposed = {
643 .output_row = output_row_transposed,
644 .get_size = get_size
647 static const struct button_mapping* get_context_map(int context)
649 return pf_contexts[context & ~CONTEXT_CUSTOM];
652 /* Create the lookup table with the scaling values for the reflections */
653 void init_reflect_table(void)
655 int i;
656 for (i = 0; i < REFLECT_HEIGHT; i++)
657 reflect_table[i] =
658 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
659 (5 * REFLECT_HEIGHT);
663 Create an index of all albums from the database.
664 Also store the album names so we can access them later.
666 int create_album_index(void)
668 buf_size -= UNIQBUF_SIZE * sizeof(long);
669 long *uniqbuf = (long *)(buf_size + (char *)buf);
670 album = ((struct album_data *)uniqbuf) - 1;
671 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
672 album_count = 0;
673 rb->tagcache_search(&tcs, tag_album);
674 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
675 unsigned int l, old_l = 0;
676 album_names = buf;
677 album[0].name_idx = 0;
678 while (rb->tagcache_get_next(&tcs))
680 buf_size -= sizeof(struct album_data);
681 l = rb->strlen(tcs.result) + 1;
682 if ( album_count > 0 )
683 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
685 if ( l > buf_size )
686 /* not enough memory */
687 return ERROR_BUFFER_FULL;
689 rb->strcpy(buf, tcs.result);
690 buf_size -= l;
691 buf = l + (char *)buf;
692 album[-album_count].seek = tcs.result_seek;
693 old_l = l;
694 album_count++;
696 rb->tagcache_search_finish(&tcs);
697 ALIGN_BUFFER(buf, buf_size, 4);
698 int i;
699 struct album_data* tmp_album = (struct album_data*)buf;
700 for (i = album_count - 1; i >= 0; i--)
701 tmp_album[i] = album[-i];
702 album = tmp_album;
703 buf = album + album_count;
704 buf_size += UNIQBUF_SIZE * sizeof(long);
705 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
709 Return a pointer to the album name of the given slide_index
711 char* get_album_name(const int slide_index)
713 return album_names + album[slide_index].name_idx;
717 Return a pointer to the track name of the active album
718 create_track_index has to be called first.
720 char* get_track_name(const int track_index)
722 if ( track_index < track_count )
723 return track_names + tracks[track_index].name_idx;
724 return 0;
728 Create the track index of the given slide_index.
730 int create_track_index(const int slide_index)
732 if ( slide_index == track_index ) {
733 return -1;
736 if (!rb->tagcache_search(&tcs, tag_title))
737 return -1;
739 int ret = 0;
740 char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4];
741 int temp_seeks[MAX_TRACKS];
743 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
744 track_count=0;
745 int string_index = 0;
746 int l, track_num, heighest_index = 0;
748 for(l=0;l<MAX_TRACKS;l++)
749 temp_titles[l][0] = '\0';
750 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
752 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
753 if (track_num >= 0)
755 rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]),
756 "%d: %s", track_num+1, tcs.result);
757 temp_seeks[track_num] = tcs.result_seek;
759 else
761 track_num = 0;
762 while (temp_titles[track_num][0] != '\0')
763 track_num++;
764 rb->strcpy(temp_titles[track_num], tcs.result);
765 temp_seeks[track_num] = tcs.result_seek;
767 if (track_num > heighest_index)
768 heighest_index = track_num;
769 track_count++;
772 rb->tagcache_search_finish(&tcs);
773 track_index = slide_index;
775 /* now fix the track list order */
776 l = 0;
777 track_count = 0;
778 while (l <= heighest_index &&
779 string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH)
781 if (temp_titles[l][0] != '\0')
783 rb->strcpy(track_names + string_index, temp_titles[l]);
784 tracks[track_count].name_idx = string_index;
785 tracks[track_count].seek = temp_seeks[l];
786 string_index += rb->strlen(temp_titles[l]) + 1;
787 track_count++;
789 l++;
791 if (ret != 0)
792 return ret;
793 else
794 return (track_count > 0) ? 0 : -1;
798 Determine filename of the album art for the given slide_index and
799 store the result in buf.
800 The algorithm looks for the first track of the given album uses
801 find_albumart to find the filename.
803 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
804 int buflen)
806 if ( slide_index == -1 )
808 rb->strncpy( buf, EMPTY_SLIDE, buflen );
811 if (!rb->tagcache_search(&tcs, tag_filename))
812 return false;
814 bool result;
815 /* find the first track of the album */
816 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
818 if ( rb->tagcache_get_next(&tcs) ) {
819 struct mp3entry id3;
820 int fd;
822 fd = rb->open(tcs.result, O_RDONLY);
823 rb->get_metadata(&id3, fd, tcs.result);
824 rb->close(fd);
825 if ( search_albumart_files(&id3, "", buf, buflen) )
826 result = true;
827 else
828 result = false;
830 else {
831 /* did not find a matching track */
832 result = false;
834 rb->tagcache_search_finish(&tcs);
835 return result;
839 Draw the PictureFlow logo
841 void draw_splashscreen(void)
843 struct screen* display = rb->screens[0];
844 const struct picture* logo = &(logos[display->screen_type]);
846 #if LCD_DEPTH > 1
847 rb->lcd_set_background(N_BRIGHT(0));
848 rb->lcd_set_foreground(N_BRIGHT(255));
849 #else
850 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
851 #endif
852 rb->lcd_clear_display();
854 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
855 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
856 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
857 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
858 #else
859 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
860 #endif
862 rb->lcd_update();
867 Draw a simple progress bar
869 void draw_progressbar(int step)
871 int txt_w, txt_h;
872 const int bar_height = 22;
873 const int w = LCD_WIDTH - 20;
874 const int x = 10;
876 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
878 int y = (LCD_HEIGHT - txt_h)/2;
880 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
881 y += (txt_h + 5);
883 #if LCD_DEPTH > 1
884 rb->lcd_set_foreground(N_BRIGHT(100));
885 #endif
886 rb->lcd_drawrect(x, y, w+2, bar_height);
887 #if LCD_DEPTH > 1
888 rb->lcd_set_foreground(N_PIX(165, 231, 82));
889 #endif
891 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
892 #if LCD_DEPTH > 1
893 rb->lcd_set_foreground(N_BRIGHT(255));
894 #endif
895 rb->lcd_update();
896 rb->yield();
900 Precomupte the album art images and store them in CACHE_PREFIX.
902 bool create_albumart_cache(void)
904 int ret;
906 int i, slides = 0;
907 struct bitmap input_bmp;
909 char pfraw_file[MAX_PATH];
910 char albumart_file[MAX_PATH];
911 unsigned int format = FORMAT_NATIVE;
912 cache_version = 0;
913 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
914 if (resize)
915 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
916 for (i=0; i < album_count; i++)
918 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
920 /* delete existing cache, so it's a true rebuild */
921 if(rb->file_exists(pfraw_file))
922 rb->remove(pfraw_file);
923 draw_progressbar(i);
924 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
925 continue;
927 input_bmp.data = buf;
928 input_bmp.width = DISPLAY_WIDTH;
929 input_bmp.height = DISPLAY_HEIGHT;
930 #if PLUGIN_BUFFER_SIZE > 0x10000
931 ret = read_image_file(albumart_file, &input_bmp,
932 buf_size, format, &format_transposed);
933 #else
934 ret = scaled_read_bmp_file(albumart_file, &input_bmp,
935 buf_size, format, &format_transposed);
936 #endif
937 if (ret <= 0) {
938 rb->splash(HZ, "Could not read bmp");
939 continue; /* skip missing/broken files */
941 if (!save_pfraw(pfraw_file, &input_bmp))
943 rb->splash(HZ, "Could not write bmp");
945 slides++;
946 if ( rb->button_get(false) == PF_MENU ) return false;
948 if ( slides == 0 ) {
949 /* Warn the user that we couldn't find any albumart */
950 rb->splash(2*HZ, "No album art found");
951 return false;
953 return true;
957 Thread used for loading and preparing bitmaps in the background
959 void thread(void)
961 long sleep_time = 5 * HZ;
962 struct queue_event ev;
963 while (1) {
964 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
965 switch (ev.id) {
966 case EV_EXIT:
967 return;
968 case EV_WAKEUP:
969 /* we just woke up */
970 break;
972 while ( load_new_slide() ) {
973 rb->yield();
974 switch (ev.id) {
975 case EV_EXIT:
976 return;
984 End the thread by posting the EV_EXIT event
986 void end_pf_thread(void)
988 if ( thread_is_running ) {
989 rb->queue_post(&thread_q, EV_EXIT, 0);
990 rb->thread_wait(thread_id);
991 /* remove the thread's queue from the broadcast list */
992 rb->queue_delete(&thread_q);
993 thread_is_running = false;
1000 Create the thread an setup the event queue
1002 bool create_pf_thread(void)
1004 /* put the thread's queue in the bcast list */
1005 rb->queue_init(&thread_q, true);
1006 if ((thread_id = rb->create_thread(
1007 thread,
1008 thread_stack,
1009 sizeof(thread_stack),
1011 "Picture load thread"
1012 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1013 PRIORITY_REALTIME + 1))
1014 IF_COP(, CPU)
1016 ) == 0) {
1017 return false;
1019 thread_is_running = true;
1020 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1021 return true;
1025 Safe the given bitmap as filename in the pfraw format
1027 bool save_pfraw(char* filename, struct bitmap *bm)
1029 struct pfraw_header bmph;
1030 bmph.width = bm->width;
1031 bmph.height = bm->height;
1032 int fh = rb->creat( filename );
1033 if( fh < 0 ) return false;
1034 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1035 int y;
1036 for( y = 0; y < bm->height; y++ )
1038 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1039 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1041 rb->close( fh );
1042 return true;
1047 * The following functions implement the linked-list-in-array used to manage
1048 * the LRU cache of slides, and the list of free cache slots.
1051 #define seek_right_while(start, cond) \
1052 ({ \
1053 int ind_, next_ = (start); \
1054 do { \
1055 ind_ = next_; \
1056 next_ = cache[ind_].next; \
1057 } while (next_ != cache_used && (cond)); \
1058 ind_; \
1061 #define seek_left_while(start, cond) \
1062 ({ \
1063 int ind_, next_ = (start); \
1064 do { \
1065 ind_ = next_; \
1066 next_ = cache[ind_].prev; \
1067 } while (ind_ != cache_used && (cond)); \
1068 ind_; \
1072 Pop the given item from the linked list starting at *head, returning the next
1073 item, or -1 if the list is now empty.
1075 static inline int lla_pop_item (int *head, int i)
1077 int prev = cache[i].prev;
1078 int next = cache[i].next;
1079 if (i == next)
1081 *head = -1;
1082 return -1;
1084 else if (i == *head)
1085 *head = next;
1086 cache[next].prev = prev;
1087 cache[prev].next = next;
1088 return next;
1093 Pop the head item from the list starting at *head, returning the index of the
1094 item, or -1 if the list is already empty.
1096 static inline int lla_pop_head (int *head)
1098 int i = *head;
1099 if (i != -1)
1100 lla_pop_item(head, i);
1101 return i;
1105 Insert the item at index i before the one at index p.
1107 static inline void lla_insert (int i, int p)
1109 int next = p;
1110 int prev = cache[next].prev;
1111 cache[next].prev = i;
1112 cache[prev].next = i;
1113 cache[i].next = next;
1114 cache[i].prev = prev;
1119 Insert the item at index i at the end of the list starting at *head.
1121 static inline void lla_insert_tail (int *head, int i)
1123 if (*head == -1)
1125 *head = i;
1126 cache[i].next = i;
1127 cache[i].prev = i;
1128 } else
1129 lla_insert(i, *head);
1133 Insert the item at index i before the one at index p.
1135 static inline void lla_insert_after(int i, int p)
1137 p = cache[p].next;
1138 lla_insert(i, p);
1143 Insert the item at index i before the one at index p in the list starting at
1144 *head
1146 static inline void lla_insert_before(int *head, int i, int p)
1148 lla_insert(i, p);
1149 if (*head == p)
1150 *head = i;
1155 Free the used slide at index i, and its buffer, and move it to the free
1156 slides list.
1158 static inline void free_slide(int i)
1160 if (cache[i].hid != empty_slide_hid)
1161 buflib_free(&buf_ctx, cache[i].hid);
1162 cache[i].index = -1;
1163 lla_pop_item(&cache_used, i);
1164 lla_insert_tail(&cache_free, i);
1165 if (cache_used == -1)
1167 cache_right_index = -1;
1168 cache_left_index = -1;
1169 cache_center_index = -1;
1175 Free one slide ranked above the given priority. If no such slide can be found,
1176 return false.
1178 static inline int free_slide_prio(int prio)
1180 if (cache_used == -1)
1181 return false;
1182 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1183 int prio_l = cache[l].index < center_index ?
1184 center_index - cache[l].index : 0;
1185 int prio_r = cache[r].index > center_index ?
1186 cache[r].index - center_index : 0;
1187 if (prio_l > prio_r)
1189 i = l;
1190 prio_max = prio_l;
1191 } else {
1192 i = r;
1193 prio_max = prio_r;
1195 if (prio_max > prio)
1197 if (i == cache_left_index)
1198 cache_left_index = cache[i].next;
1199 if (i == cache_right_index)
1200 cache_right_index = cache[i].prev;
1201 free_slide(i);
1202 return true;
1203 } else
1204 return false;
1208 Read the pfraw image given as filename and return the hid of the buffer
1210 int read_pfraw(char* filename, int prio)
1212 struct pfraw_header bmph;
1213 int fh = rb->open(filename, O_RDONLY);
1214 if( fh < 0 )
1215 return empty_slide_hid;
1216 else
1217 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1219 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1220 bmph.width * bmph.height;
1222 int hid;
1223 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1225 if (!hid) {
1226 rb->close( fh );
1227 return 0;
1230 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1232 bm->width = bmph.width;
1233 bm->height = bmph.height;
1234 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1236 int y;
1237 for( y = 0; y < bm->height; y++ )
1239 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1240 data += bm->width;
1242 rb->close( fh );
1243 return hid;
1248 Load the surface for the given slide_index into the cache at cache_index.
1250 static inline bool load_and_prepare_surface(const int slide_index,
1251 const int cache_index,
1252 const int prio)
1254 char tmp_path_name[MAX_PATH+1];
1255 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1256 slide_index);
1258 int hid = read_pfraw(tmp_path_name, prio);
1259 if (!hid)
1260 return false;
1262 cache[cache_index].hid = hid;
1264 if ( cache_index < SLIDE_CACHE_SIZE ) {
1265 cache[cache_index].index = slide_index;
1268 return true;
1273 Load the "next" slide that we can load, freeing old slides if needed, provided
1274 that they are further from center_index than the current slide
1276 bool load_new_slide(void)
1278 int i = -1;
1279 if (cache_center_index != -1)
1281 int next, prev;
1282 if (cache[cache_center_index].index != center_index)
1284 if (cache[cache_center_index].index < center_index)
1286 cache_center_index = seek_right_while(cache_center_index,
1287 cache[next_].index <= center_index);
1288 prev = cache_center_index;
1289 next = cache[cache_center_index].next;
1291 else
1293 cache_center_index = seek_left_while(cache_center_index,
1294 cache[next_].index >= center_index);
1295 next = cache_center_index;
1296 prev = cache[cache_center_index].prev;
1298 if (cache[cache_center_index].index != center_index)
1300 if (cache_free == -1)
1301 free_slide_prio(0);
1302 i = lla_pop_head(&cache_free);
1303 if (!load_and_prepare_surface(center_index, i, 0))
1304 goto fail_and_refree;
1305 if (cache[next].index == -1)
1307 if (cache[prev].index == -1)
1308 goto insert_first_slide;
1309 else
1310 next = cache[prev].next;
1312 lla_insert(i, next);
1313 if (cache[i].index < cache[cache_used].index)
1314 cache_used = i;
1315 cache_center_index = i;
1316 cache_left_index = i;
1317 cache_right_index = i;
1318 return true;
1321 if (cache[cache_left_index].index >
1322 cache[cache_center_index].index)
1323 cache_left_index = cache_center_index;
1324 if (cache[cache_right_index].index <
1325 cache[cache_center_index].index)
1326 cache_right_index = cache_center_index;
1327 cache_left_index = seek_left_while(cache_left_index,
1328 cache[ind_].index - 1 == cache[next_].index);
1329 cache_right_index = seek_right_while(cache_right_index,
1330 cache[ind_].index - 1 == cache[next_].index);
1331 int prio_l = cache[cache_center_index].index -
1332 cache[cache_left_index].index + 1;
1333 int prio_r = cache[cache_right_index].index -
1334 cache[cache_center_index].index + 1;
1335 if ((prio_l < prio_r ||
1336 cache[cache_right_index].index >= number_of_slides) &&
1337 cache[cache_left_index].index > 0)
1339 if (cache_free == -1 && !free_slide_prio(prio_l))
1340 return false;
1341 i = lla_pop_head(&cache_free);
1342 if (load_and_prepare_surface(cache[cache_left_index].index
1343 - 1, i, prio_l))
1345 lla_insert_before(&cache_used, i, cache_left_index);
1346 cache_left_index = i;
1347 return true;
1349 } else if(cache[cache_right_index].index < number_of_slides - 1)
1351 if (cache_free == -1 && !free_slide_prio(prio_r))
1352 return false;
1353 i = lla_pop_head(&cache_free);
1354 if (load_and_prepare_surface(cache[cache_right_index].index
1355 + 1, i, prio_r))
1357 lla_insert_after(i, cache_right_index);
1358 cache_right_index = i;
1359 return true;
1362 } else {
1363 i = lla_pop_head(&cache_free);
1364 if (load_and_prepare_surface(center_index, i, 0))
1366 insert_first_slide:
1367 cache[i].next = i;
1368 cache[i].prev = i;
1369 cache_center_index = i;
1370 cache_left_index = i;
1371 cache_right_index = i;
1372 cache_used = i;
1373 return true;
1376 fail_and_refree:
1377 if (i != -1)
1379 lla_insert_tail(&cache_free, i);
1381 return false;
1386 Get a slide from the buffer
1388 static inline struct dim *get_slide(const int hid)
1390 if (!hid)
1391 return NULL;
1393 struct dim *bmp;
1395 bmp = buflib_get_data(&buf_ctx, hid);
1397 return bmp;
1402 Return the requested surface
1404 static inline struct dim *surface(const int slide_index)
1406 if (slide_index < 0)
1407 return 0;
1408 if (slide_index >= number_of_slides)
1409 return 0;
1410 int i;
1411 if ((i = cache_used ) != -1)
1413 do {
1414 if (cache[i].index == slide_index)
1415 return get_slide(cache[i].hid);
1416 i = cache[i].next;
1417 } while (i != cache_used);
1419 return get_slide(empty_slide_hid);
1423 adjust slides so that they are in "steady state" position
1425 void reset_slides(void)
1427 center_slide.angle = 0;
1428 center_slide.cx = 0;
1429 center_slide.cy = 0;
1430 center_slide.distance = 0;
1431 center_slide.slide_index = center_index;
1433 int i;
1434 for (i = 0; i < num_slides; i++) {
1435 struct slide_data *si = &left_slides[i];
1436 si->angle = itilt;
1437 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1438 si->cy = offsetY;
1439 si->slide_index = center_index - 1 - i;
1440 si->distance = 0;
1443 for (i = 0; i < num_slides; i++) {
1444 struct slide_data *si = &right_slides[i];
1445 si->angle = -itilt;
1446 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1447 si->cy = offsetY;
1448 si->slide_index = center_index + 1 + i;
1449 si->distance = 0;
1455 Updates look-up table and other stuff necessary for the rendering.
1456 Call this when the viewport size or slide dimension is changed.
1458 * To calculate the offset that will provide the proper margin, we use the same
1459 * projection used to render the slides. The solution for xc, the slide center,
1460 * is:
1461 * xp * (zo + xs * sin(r))
1462 * xc = xp - xs * cos(r) + ───────────────────────
1464 * TODO: support moving the side slides toward or away from the camera
1466 void recalc_offsets(void)
1468 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1469 PFreal zo;
1470 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1471 PFREAL_ONE) * zoom / 100;
1472 PFreal cosr, sinr;
1474 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1475 cosr = fcos(-itilt);
1476 sinr = fsin(-itilt);
1477 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1478 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1479 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1480 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1481 / CAM_DIST;
1482 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1487 Fade the given color by spreading the fb_data (ushort)
1488 to an uint, multiply and compress the result back to a ushort.
1490 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1491 static inline unsigned fade_color(pix_t c, unsigned a)
1493 unsigned int result;
1494 c = swap16(c);
1495 a = (a + 2) & 0x1fc;
1496 result = ((c & 0xf81f) * a) & 0xf81f00;
1497 result |= ((c & 0x7e0) * a) & 0x7e000;
1498 result >>= 8;
1499 return swap16(result);
1501 #elif LCD_PIXELFORMAT == RGB565
1502 static inline unsigned fade_color(pix_t c, unsigned a)
1504 unsigned int result;
1505 a = (a + 2) & 0x1fc;
1506 result = ((c & 0xf81f) * a) & 0xf81f00;
1507 result |= ((c & 0x7e0) * a) & 0x7e000;
1508 result >>= 8;
1509 return result;
1511 #else
1512 static inline unsigned fade_color(pix_t c, unsigned a)
1514 unsigned val = c;
1515 return MULUQ(val, a) >> 8;
1517 #endif
1520 * Render a single slide
1521 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1522 * on the slide from its center, zo is the slide's depth offset from the plane
1523 * of the display, r is the angle at which the slide is tilted, and xp is the
1524 * point on the display corresponding to xs on the slide, the projection
1525 * formulas are:
1527 * z * (xc + xs * cos(r))
1528 * xp = ──────────────────────
1529 * z + zo + xs * sin(r)
1531 * z * (xc - xp) - xp * zo
1532 * xs = ────────────────────────
1533 * xp * sin(r) - z * cos(r)
1535 * We use the xp projection once, to find the left edge of the slide on the
1536 * display. From there, we use the xs reverse projection to find the horizontal
1537 * offset from the slide center of each column on the screen, until we reach
1538 * the right edge of the slide, or the screen. The reverse projection can be
1539 * optimized by saving the numerator and denominator of the fraction, which can
1540 * then be incremented by (z + zo) and sin(r) respectively.
1542 void render_slide(struct slide_data *slide, const int alpha)
1544 struct dim *bmp = surface(slide->slide_index);
1545 if (!bmp) {
1546 return;
1548 if (slide->angle > 255 || slide->angle < -255)
1549 return;
1550 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1552 const int sw = bmp->width;
1553 const int sh = bmp->height;
1554 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1555 const int w = LCD_WIDTH;
1557 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1559 if (alpha == 256) { /* opaque -> copy table */
1560 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1561 } else { /* precalculate faded table */
1562 int i, lalpha;
1563 for (i = 0; i < REFLECT_HEIGHT; i++) {
1564 lalpha = reflect_table[i];
1565 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1569 PFreal cosr = fcos(slide->angle);
1570 PFreal sinr = fsin(slide->angle);
1571 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1572 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1573 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1574 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1575 (CAM_DIST_R + zo + fmul(xs,sinr)));
1577 /* Since we're finding the screen position of the left edge of the slide,
1578 * we round up.
1580 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1581 >> PFREAL_SHIFT;
1582 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1583 if (xi >= w) {
1584 return;
1586 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1587 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1588 xs = fdiv(xsnum, xsden);
1590 xsnumi = -CAM_DIST_R - zo;
1591 xsdeni = sinr;
1592 int x;
1593 int dy = PFREAL_ONE;
1594 for (x = xi; x < w; x++) {
1595 int column = (xs - slide_left) / PFREAL_ONE;
1596 if (column >= sw)
1597 break;
1598 if (zo || slide->angle)
1599 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1601 const pix_t *ptr = &src[column * bmp->height];
1602 const int pixelstep = BUFFER_WIDTH;
1604 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1605 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1606 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1608 if (alpha == 256) {
1609 while (p >= plim) {
1610 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1611 p -= dy;
1612 pixel -= pixelstep;
1614 } else {
1615 while (p >= plim) {
1616 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1617 p -= dy;
1618 pixel -= pixelstep;
1621 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1622 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1623 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1624 p + (LCD_HEIGHT/2) * dy);
1625 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1627 if (alpha == 256) {
1628 while (p < plim) {
1629 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1630 p += dy;
1631 pixel += pixelstep;
1633 } else {
1634 while (p < plim) {
1635 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1636 p += dy;
1637 pixel += pixelstep;
1640 while (p < plim2) {
1641 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1642 int lalpha = reftab[ty];
1643 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1644 p += dy;
1645 pixel += pixelstep;
1648 if (zo || slide->angle)
1650 xsnum += xsnumi;
1651 xsden += xsdeni;
1652 xs = fdiv(xsnum, xsden);
1653 } else
1654 xs += PFREAL_ONE;
1657 /* let the music play... */
1658 rb->yield();
1659 return;
1664 Jump the the given slide_index
1666 static inline void set_current_slide(const int slide_index)
1668 int old_center_index = center_index;
1669 step = 0;
1670 center_index = fbound(slide_index, 0, number_of_slides - 1);
1671 if (old_center_index != center_index)
1672 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1673 target = center_index;
1674 slide_frame = slide_index << 16;
1675 reset_slides();
1679 Start the animation for changing slides
1681 void start_animation(void)
1683 step = (target < center_slide.slide_index) ? -1 : 1;
1684 pf_state = pf_scrolling;
1688 Go to the previous slide
1690 void show_previous_slide(void)
1692 if (step == 0) {
1693 if (center_index > 0) {
1694 target = center_index - 1;
1695 start_animation();
1697 } else if ( step > 0 ) {
1698 target = center_index;
1699 start_animation();
1700 } else {
1701 target = fmax(0, center_index - 2);
1707 Go to the next slide
1709 void show_next_slide(void)
1711 if (step == 0) {
1712 if (center_index < number_of_slides - 1) {
1713 target = center_index + 1;
1714 start_animation();
1716 } else if ( step < 0 ) {
1717 target = center_index;
1718 start_animation();
1719 } else {
1720 target = fmin(center_index + 2, number_of_slides - 1);
1726 Render the slides. Updates only the offscreen buffer.
1728 void render_all_slides(void)
1730 MYLCD(set_background)(G_BRIGHT(0));
1731 /* TODO: Optimizes this by e.g. invalidating rects */
1732 MYLCD(clear_display)();
1734 int nleft = num_slides;
1735 int nright = num_slides;
1737 int index;
1738 if (step == 0) {
1739 /* no animation, boring plain rendering */
1740 for (index = nleft - 2; index >= 0; index--) {
1741 int alpha = (index < nleft - 2) ? 256 : 128;
1742 alpha -= extra_fade;
1743 if (alpha > 0 )
1744 render_slide(&left_slides[index], alpha);
1746 for (index = nright - 2; index >= 0; index--) {
1747 int alpha = (index < nright - 2) ? 256 : 128;
1748 alpha -= extra_fade;
1749 if (alpha > 0 )
1750 render_slide(&right_slides[index], alpha);
1752 } else {
1753 /* the first and last slide must fade in/fade out */
1754 for (index = nleft - 1; index >= 0; index--) {
1755 int alpha = 256;
1756 if (index == nleft - 1)
1757 alpha = (step > 0) ? 0 : 128 - fade / 2;
1758 if (index == nleft - 2)
1759 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1760 if (index == nleft - 3)
1761 alpha = (step > 0) ? 256 - fade / 2 : 256;
1762 render_slide(&left_slides[index], alpha);
1764 for (index = nright - 1; index >= 0; index--) {
1765 int alpha = (index < nright - 2) ? 256 : 128;
1766 if (index == nright - 1)
1767 alpha = (step > 0) ? fade / 2 : 0;
1768 if (index == nright - 2)
1769 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1770 if (index == nright - 3)
1771 alpha = (step > 0) ? 256 : 128 + fade / 2;
1772 render_slide(&right_slides[index], alpha);
1775 render_slide(&center_slide, 256);
1780 Updates the animation effect. Call this periodically from a timer.
1782 void update_scroll_animation(void)
1784 if (step == 0)
1785 return;
1787 int speed = 16384;
1788 int i;
1790 /* deaccelerate when approaching the target */
1791 if (true) {
1792 const int max = 2 * 65536;
1794 int fi = slide_frame;
1795 fi -= (target << 16);
1796 if (fi < 0)
1797 fi = -fi;
1798 fi = fmin(fi, max);
1800 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1801 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1804 slide_frame += speed * step;
1806 int index = slide_frame >> 16;
1807 int pos = slide_frame & 0xffff;
1808 int neg = 65536 - pos;
1809 int tick = (step < 0) ? neg : pos;
1810 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1812 /* the leftmost and rightmost slide must fade away */
1813 fade = pos / 256;
1815 if (step < 0)
1816 index++;
1817 if (center_index != index) {
1818 center_index = index;
1819 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1820 slide_frame = index << 16;
1821 center_slide.slide_index = center_index;
1822 for (i = 0; i < num_slides; i++)
1823 left_slides[i].slide_index = center_index - 1 - i;
1824 for (i = 0; i < num_slides; i++)
1825 right_slides[i].slide_index = center_index + 1 + i;
1828 center_slide.angle = (step * tick * itilt) >> 16;
1829 center_slide.cx = -step * fmul(offsetX, ftick);
1830 center_slide.cy = fmul(offsetY, ftick);
1832 if (center_index == target) {
1833 reset_slides();
1834 pf_state = pf_idle;
1835 step = 0;
1836 fade = 256;
1837 return;
1840 for (i = 0; i < num_slides; i++) {
1841 struct slide_data *si = &left_slides[i];
1842 si->angle = itilt;
1843 si->cx =
1844 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1845 * slide_spacing * ftick);
1846 si->cy = offsetY;
1849 for (i = 0; i < num_slides; i++) {
1850 struct slide_data *si = &right_slides[i];
1851 si->angle = -itilt;
1852 si->cx =
1853 offsetX + slide_spacing * i * PFREAL_ONE - step
1854 * slide_spacing * ftick;
1855 si->cy = offsetY;
1858 if (step > 0) {
1859 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1860 right_slides[0].angle = -(neg * itilt) >> 16;
1861 right_slides[0].cx = fmul(offsetX, ftick);
1862 right_slides[0].cy = fmul(offsetY, ftick);
1863 } else {
1864 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1865 left_slides[0].angle = (pos * itilt) >> 16;
1866 left_slides[0].cx = -fmul(offsetX, ftick);
1867 left_slides[0].cy = fmul(offsetY, ftick);
1870 /* must change direction ? */
1871 if (target < index)
1872 if (step > 0)
1873 step = -1;
1874 if (target > index)
1875 if (step < 0)
1876 step = 1;
1881 Cleanup the plugin
1883 void cleanup(void *parameter)
1885 (void) parameter;
1886 /* Turn on backlight timeout (revert to settings) */
1887 backlight_use_settings(); /* backlight control in lib/helper.c */
1889 #ifdef USEGSLIB
1890 grey_release();
1891 #endif
1895 Create the "?" slide, that is shown while loading
1896 or when no cover was found.
1898 int create_empty_slide(bool force)
1900 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1901 struct bitmap input_bmp;
1902 int ret;
1903 input_bmp.width = DISPLAY_WIDTH;
1904 input_bmp.height = DISPLAY_HEIGHT;
1905 #if LCD_DEPTH > 1
1906 input_bmp.format = FORMAT_NATIVE;
1907 #endif
1908 input_bmp.data = (char*)buf;
1909 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1910 buf_size,
1911 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1912 &format_transposed);
1913 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1914 return false;
1917 return true;
1921 Shows the album name setting menu
1923 int album_name_menu(void)
1925 int selection = show_album_name;
1927 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
1928 "Hide album title", "Show at the bottom", "Show at the top");
1929 rb->do_menu(&album_name_menu, &selection, NULL, false);
1931 show_album_name = selection;
1932 return GO_TO_PREVIOUS;
1936 Shows the settings menu
1938 int settings_menu(void)
1940 int selection = 0;
1941 bool old_val;
1943 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1944 "Spacing", "Centre margin", "Number of slides", "Zoom",
1945 "Show album title", "Resize Covers", "Rebuild cache");
1947 do {
1948 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1949 switch(selection) {
1950 case 0:
1951 rb->set_bool("Show FPS", &show_fps);
1952 reset_track_list();
1953 break;
1955 case 1:
1956 rb->set_int("Spacing between slides", "", 1,
1957 &slide_spacing,
1958 NULL, 1, 0, 100, NULL );
1959 recalc_offsets();
1960 reset_slides();
1961 break;
1963 case 2:
1964 rb->set_int("Centre margin", "", 1,
1965 &center_margin,
1966 NULL, 1, 0, 80, NULL );
1967 recalc_offsets();
1968 reset_slides();
1969 break;
1971 case 3:
1972 rb->set_int("Number of slides", "", 1, &num_slides,
1973 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1974 recalc_offsets();
1975 reset_slides();
1976 break;
1978 case 4:
1979 rb->set_int("Zoom", "", 1, &zoom,
1980 NULL, 1, 10, 300, NULL );
1981 recalc_offsets();
1982 reset_slides();
1983 break;
1984 case 5:
1985 album_name_menu();
1986 reset_track_list();
1987 recalc_offsets();
1988 reset_slides();
1989 break;
1990 case 6:
1991 old_val = resize;
1992 rb->set_bool("Resize Covers", &resize);
1993 if (old_val == resize) /* changed? */
1994 break;
1995 /* fallthrough if changed, since cache needs to be rebuilt */
1996 case 7:
1997 cache_version = 0;
1998 rb->remove(EMPTY_SLIDE);
1999 rb->splash(HZ, "Cache will be rebuilt on next restart");
2000 break;
2002 case MENU_ATTACHED_USB:
2003 return PLUGIN_USB_CONNECTED;
2005 } while ( selection >= 0 );
2006 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2007 return 0;
2011 Show the main menu
2013 int main_menu(void)
2015 int selection = 0;
2016 int result;
2018 #if LCD_DEPTH > 1
2019 rb->lcd_set_foreground(N_BRIGHT(255));
2020 #endif
2022 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2023 "Settings", "Return", "Quit");
2024 while (1) {
2025 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2026 case 0:
2027 result = settings_menu();
2028 if ( result != 0 ) return result;
2029 break;
2031 case 1:
2032 return 0;
2034 case 2:
2035 return -1;
2037 case MENU_ATTACHED_USB:
2038 return PLUGIN_USB_CONNECTED;
2040 default:
2041 return 0;
2047 Animation step for zooming into the current cover
2049 void update_cover_in_animation(void)
2051 cover_animation_keyframe++;
2052 if( cover_animation_keyframe < 20 ) {
2053 center_slide.distance-=5;
2054 center_slide.angle+=1;
2055 extra_fade += 13;
2057 else if( cover_animation_keyframe < 35 ) {
2058 center_slide.angle+=16;
2060 else {
2061 cover_animation_keyframe = 0;
2062 pf_state = pf_show_tracks;
2067 Animation step for zooming out the current cover
2069 void update_cover_out_animation(void)
2071 cover_animation_keyframe++;
2072 if( cover_animation_keyframe <= 15 ) {
2073 center_slide.angle-=16;
2075 else if( cover_animation_keyframe < 35 ) {
2076 center_slide.distance+=5;
2077 center_slide.angle-=1;
2078 extra_fade -= 13;
2080 else {
2081 cover_animation_keyframe = 0;
2082 pf_state = pf_idle;
2087 Draw a blue gradient at y with height h
2089 static inline void draw_gradient(int y, int h)
2091 static int r, inc, c;
2092 inc = (100 << 8) / h;
2093 c = 0;
2094 selected_track_pulse = (selected_track_pulse+1) % 10;
2095 int c2 = selected_track_pulse - 5;
2096 for (r=0; r<h; r++) {
2097 #ifdef HAVE_LCD_COLOR
2098 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2099 c2+250-(c >> 8)));
2100 #else
2101 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2102 #endif
2103 MYLCD(hline)(0, LCD_WIDTH, r+y);
2104 if ( r > h/2 )
2105 c-=inc;
2106 else
2107 c+=inc;
2112 static void track_list_yh(int char_height)
2114 switch (show_album_name)
2116 case album_name_hide:
2117 track_list_y = (show_fps ? char_height : 0);
2118 track_list_h = LCD_HEIGHT - track_list_y;
2119 break;
2120 case album_name_bottom:
2121 track_list_y = (show_fps ? char_height : 0);
2122 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2123 break;
2124 default: /* case album_name_top */
2125 track_list_y = char_height * 2;
2126 track_list_h = LCD_HEIGHT - track_list_y -
2127 (show_fps ? char_height : 0);
2128 break;
2133 Reset the track list after a album change
2135 void reset_track_list(void)
2137 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2138 track_list_yh(albumtxt_h);
2139 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2140 start_index_track_list = 0;
2141 track_scroll_index = 0;
2142 track_scroll_dir = 1;
2143 selected_track = 0;
2145 /* let the tracklist start more centered
2146 * if the screen isn't filled with tracks */
2147 if (track_count*albumtxt_h < track_list_h)
2149 track_list_h = track_count * albumtxt_h;
2150 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2155 Display the list of tracks
2157 void show_track_list(void)
2159 MYLCD(clear_display)();
2160 if ( center_slide.slide_index != track_index ) {
2161 create_track_index(center_slide.slide_index);
2162 reset_track_list();
2164 static int titletxt_w, titletxt_x, color, titletxt_h;
2165 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2167 int titletxt_y = track_list_y;
2168 int track_i;
2169 track_i = start_index_track_list;
2170 for (;track_i < track_list_visible_entries+start_index_track_list;
2171 track_i++)
2173 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2174 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2175 if ( track_i == selected_track ) {
2176 draw_gradient(titletxt_y, titletxt_h);
2177 MYLCD(set_foreground)(G_BRIGHT(255));
2178 if (titletxt_w > LCD_WIDTH ) {
2179 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2180 track_scroll_dir = 1;
2181 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2182 track_scroll_index += track_scroll_dir*2;
2183 titletxt_x = track_scroll_index;
2185 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2187 else {
2188 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2189 MYLCD(set_foreground)(G_BRIGHT(color));
2190 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2192 titletxt_y += titletxt_h;
2196 void select_next_track(void)
2198 if ( selected_track < track_count - 1 ) {
2199 selected_track++;
2200 track_scroll_index = 0;
2201 track_scroll_dir = 1;
2202 if (selected_track==(track_list_visible_entries+start_index_track_list))
2203 start_index_track_list++;
2207 void select_prev_track(void)
2209 if (selected_track > 0 ) {
2210 if (selected_track==start_index_track_list) start_index_track_list--;
2211 track_scroll_index = 0;
2212 track_scroll_dir = 1;
2213 selected_track--;
2218 Draw the current album name
2220 void draw_album_text(void)
2222 if (0 == show_album_name)
2223 return;
2225 int albumtxt_w, albumtxt_h;
2226 int albumtxt_y = 0;
2228 char *albumtxt;
2229 int c;
2230 /* Draw album text */
2231 if ( pf_state == pf_scrolling ) {
2232 c = ((slide_frame & 0xffff )/ 255);
2233 if (step < 0) c = 255-c;
2234 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2235 albumtxt = get_album_name(center_index+step);
2236 c = (c-128)*2;
2238 else {
2239 albumtxt = get_album_name(center_index);
2240 c = (128-c)*2;
2243 else {
2244 c= 255;
2245 albumtxt = get_album_name(center_index);
2248 MYLCD(set_foreground)(G_BRIGHT(c));
2249 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2250 if (center_index != prev_center_index) {
2251 albumtxt_x = 0;
2252 albumtxt_dir = -1;
2253 prev_center_index = center_index;
2256 if (show_album_name == album_name_top)
2257 albumtxt_y = albumtxt_h / 2;
2258 else
2259 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2261 if (albumtxt_w > LCD_WIDTH ) {
2262 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2263 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2264 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2265 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2266 albumtxt_x += albumtxt_dir;
2269 else {
2270 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2278 Main function that also contain the main plasma
2279 algorithm.
2281 int main(void)
2283 int ret;
2285 rb->lcd_setfont(FONT_UI);
2286 draw_splashscreen();
2288 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2289 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2290 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2291 return PLUGIN_ERROR;
2295 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2297 init_reflect_table();
2299 ALIGN_BUFFER(buf, buf_size, 4);
2300 ret = create_album_index();
2301 if (ret == ERROR_BUFFER_FULL) {
2302 rb->splash(HZ, "Not enough memory for album names");
2303 return PLUGIN_ERROR;
2304 } else if (ret == ERROR_NO_ALBUMS) {
2305 rb->splash(HZ, "No albums found. Please enable database");
2306 return PLUGIN_ERROR;
2309 ALIGN_BUFFER(buf, buf_size, 4);
2310 number_of_slides = album_count;
2311 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2312 rb->splash(HZ, "Could not create album art cache");
2313 return PLUGIN_ERROR;
2316 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2317 rb->splash(HZ, "Could not load the empty slide");
2318 return PLUGIN_ERROR;
2320 cache_version = CACHE_VERSION;
2321 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2324 #ifdef USEGSLIB
2325 long grey_buf_used;
2326 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2327 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2329 rb->splash(HZ, "Greylib init failed!");
2330 return PLUGIN_ERROR;
2332 grey_setfont(FONT_UI);
2333 buf_size -= grey_buf_used;
2334 buf = (void*)(grey_buf_used + (char*)buf);
2335 #endif
2336 buflib_init(&buf_ctx, (void *)buf, buf_size);
2338 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2340 rb->splash(HZ, "Unable to load empty slide image");
2341 return PLUGIN_ERROR;
2344 if (!create_pf_thread()) {
2345 rb->splash(HZ, "Cannot create thread!");
2346 return PLUGIN_ERROR;
2349 int i;
2351 /* initialize */
2352 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2353 cache[i].hid = 0;
2354 cache[i].index = 0;
2355 cache[i].next = i + 1;
2356 cache[i].prev = i - 1;
2358 cache[0].prev = i - 1;
2359 cache[i - 1].next = 0;
2360 cache_free = 0;
2361 buffer = LCD_BUF;
2363 pf_state = pf_idle;
2365 track_index = -1;
2366 extra_fade = 0;
2367 slide_frame = 0;
2368 step = 0;
2369 target = 0;
2370 fade = 256;
2372 recalc_offsets();
2373 reset_slides();
2375 char fpstxt[10];
2376 int button;
2378 int frames = 0;
2379 long last_update = *rb->current_tick;
2380 long current_update;
2381 long update_interval = 100;
2382 int fps = 0;
2383 int fpstxt_y;
2385 bool instant_update;
2386 #ifdef USEGSLIB
2387 grey_show(true);
2388 grey_set_drawmode(DRMODE_FG);
2389 #endif
2390 rb->lcd_set_drawmode(DRMODE_FG);
2391 while (true) {
2392 current_update = *rb->current_tick;
2393 frames++;
2395 /* Initial rendering */
2396 instant_update = false;
2398 /* Handle states */
2399 switch ( pf_state ) {
2400 case pf_scrolling:
2401 update_scroll_animation();
2402 render_all_slides();
2403 instant_update = true;
2404 break;
2405 case pf_cover_in:
2406 update_cover_in_animation();
2407 render_all_slides();
2408 instant_update = true;
2409 break;
2410 case pf_cover_out:
2411 update_cover_out_animation();
2412 render_all_slides();
2413 instant_update = true;
2414 break;
2415 case pf_show_tracks:
2416 show_track_list();
2417 break;
2418 case pf_idle:
2419 render_all_slides();
2420 break;
2423 /* Calculate FPS */
2424 if (current_update - last_update > update_interval) {
2425 fps = frames * HZ / (current_update - last_update);
2426 last_update = current_update;
2427 frames = 0;
2429 /* Draw FPS */
2430 if (show_fps)
2432 #ifdef USEGSLIB
2433 MYLCD(set_foreground)(G_BRIGHT(255));
2434 #else
2435 MYLCD(set_foreground)(G_PIX(255,0,0));
2436 #endif
2437 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2438 if (show_album_name == album_name_top)
2439 fpstxt_y = LCD_HEIGHT -
2440 rb->screens[SCREEN_MAIN]->getcharheight();
2441 else
2442 fpstxt_y = 0;
2443 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2445 draw_album_text();
2448 /* Copy offscreen buffer to LCD and give time to other threads */
2449 MYLCD(update)();
2450 rb->yield();
2452 /*/ Handle buttons */
2453 button = rb->get_custom_action(CONTEXT_CUSTOM|
2454 (pf_state == pf_show_tracks ? 1 : 0),
2455 instant_update ? 0 : HZ/16,
2456 get_context_map);
2458 switch (button) {
2459 case PF_QUIT:
2460 return PLUGIN_OK;
2462 case PF_BACK:
2463 if ( pf_state == pf_show_tracks )
2464 pf_state = pf_cover_out;
2465 if (pf_state == pf_idle || pf_state == pf_scrolling)
2466 return PLUGIN_OK;
2467 break;
2469 case PF_MENU:
2470 #ifdef USEGSLIB
2471 grey_show(false);
2472 #endif
2473 ret = main_menu();
2474 if ( ret == -1 ) return PLUGIN_OK;
2475 if ( ret != 0 ) return i;
2476 #ifdef USEGSLIB
2477 grey_show(true);
2478 #endif
2479 MYLCD(set_drawmode)(DRMODE_FG);
2480 break;
2482 case PF_NEXT:
2483 case PF_NEXT_REPEAT:
2484 if ( pf_state == pf_show_tracks )
2485 select_next_track();
2486 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2487 show_next_slide();
2488 break;
2490 case PF_PREV:
2491 case PF_PREV_REPEAT:
2492 if ( pf_state == pf_show_tracks )
2493 select_prev_track();
2494 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2495 show_previous_slide();
2496 break;
2498 case PF_SELECT:
2499 if ( pf_state == pf_idle ) {
2500 pf_state = pf_cover_in;
2502 break;
2504 default:
2505 if (rb->default_event_handler_ex(button, cleanup, NULL)
2506 == SYS_USB_CONNECTED)
2507 return PLUGIN_USB_CONNECTED;
2508 break;
2515 /*************************** Plugin entry point ****************************/
2517 enum plugin_status plugin_start(const void *parameter)
2519 int ret;
2520 (void) parameter;
2521 #if LCD_DEPTH > 1
2522 rb->lcd_set_backdrop(NULL);
2523 #endif
2524 /* Turn off backlight timeout */
2525 backlight_force_on(); /* backlight control in lib/helper.c */
2526 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2527 rb->cpu_boost(true);
2528 #endif
2529 #if PLUGIN_BUFFER_SIZE > 0x10000
2530 buf = rb->plugin_get_buffer(&buf_size);
2531 #else
2532 buf = rb->plugin_get_audio_buffer(&buf_size);
2533 #endif
2534 ret = main();
2535 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2536 rb->cpu_boost(false);
2537 #endif
2538 if ( ret == PLUGIN_OK ) {
2539 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2540 CONFIG_VERSION))
2542 rb->splash(HZ, "Error writing config.");
2543 ret = PLUGIN_ERROR;
2547 end_pf_thread();
2548 cleanup(NULL);
2549 return ret;