pictureflow: use correct priority to free slides when extending cache on right, catch...
[kugel-rb.git] / apps / plugins / pictureflow.c
blob58d2869ecc9339ed4088ac6ba1b15909ab4a3012
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/pluginlib_actions.h"
29 #include "lib/helper.h"
30 #include "lib/configfile.h"
31 #include "lib/picture.h"
32 #include "pluginbitmaps/pictureflow_logo.h"
33 #include "lib/grey.h"
34 #include "lib/feature_wrappers.h"
35 #include "lib/buflib.h"
37 PLUGIN_HEADER
39 /******************************* Globals ***********************************/
41 const struct button_mapping *plugin_contexts[]
42 = {generic_actions, generic_directions};
44 #define NB_ACTION_CONTEXTS sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
46 #if LCD_DEPTH < 8
47 #if LCD_DEPTH > 1
48 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
49 #else
50 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
51 #endif
52 #define USEGSLIB
53 GREY_INFO_STRUCT
54 #define LCD_BUF _grey_info.buffer
55 #define MYLCD(fn) grey_ ## fn
56 #define G_PIX(r,g,b) \
57 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
58 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
59 #define G_BRIGHT(y) (y)
60 #define BUFFER_WIDTH _grey_info.width
61 #define BUFFER_HEIGHT _grey_info.height
62 typedef unsigned char pix_t;
63 #else
64 #define LCD_BUF rb->lcd_framebuffer
65 #define MYLCD(fn) rb->lcd_ ## fn
66 #define G_PIX LCD_RGBPACK
67 #define N_PIX LCD_RGBPACK
68 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
69 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
70 #define BUFFER_WIDTH LCD_WIDTH
71 #define BUFFER_HEIGHT LCD_HEIGHT
72 typedef fb_data pix_t;
73 #endif
75 #ifdef HAVE_SCROLLWHEEL
76 #define PICTUREFLOW_NEXT_ALBUM PLA_DOWN
77 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_DOWN_REPEAT
78 #define PICTUREFLOW_PREV_ALBUM PLA_UP
79 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_UP_REPEAT
80 #else
81 #define PICTUREFLOW_NEXT_ALBUM PLA_RIGHT
82 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_RIGHT_REPEAT
83 #define PICTUREFLOW_PREV_ALBUM PLA_LEFT
84 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_LEFT_REPEAT
85 #define PICTUREFLOW_NEXT_TRACK PLA_DOWN
86 #define PICTUREFLOW_NEXT_TRACK_REPEAT PLA_DOWN_REPEAT
87 #define PICTUREFLOW_PREV_TRACK PLA_UP
88 #define PICTUREFLOW_PREV_TRACK_REPEAT PLA_UP_REPEAT
89 #endif
90 #define PICTUREFLOW_MENU PLA_MENU
91 #define PICTUREFLOW_QUIT PLA_QUIT
92 #define PICTUREFLOW_SELECT_ALBUM PLA_FIRE
95 /* for fixed-point arithmetic, we need minimum 32-bit long
96 long long (64-bit) might be useful for multiplication and division */
97 #define PFreal long
98 #define PFREAL_SHIFT 10
99 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
100 #define PFREAL_ONE (1 << PFREAL_SHIFT)
101 #define PFREAL_HALF (PFREAL_ONE >> 1)
104 #define IANGLE_MAX 1024
105 #define IANGLE_MASK 1023
107 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
108 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
109 #define DISPLAY_HEIGHT REFLECT_TOP
110 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
111 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
112 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
113 (REFLECT_HEIGHT * 5))
114 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
115 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
116 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
117 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
118 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
120 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
122 #define MAX_SLIDES_COUNT 10
124 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
125 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
127 #define EV_EXIT 9999
128 #define EV_WAKEUP 1337
130 /* maximum number of albums */
132 #define MAX_TRACKS 50
133 #define AVG_TRACK_NAME_LENGTH 20
136 #define UNIQBUF_SIZE (64*1024)
138 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
139 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
141 /* Error return values */
142 #define ERROR_NO_ALBUMS -1
143 #define ERROR_BUFFER_FULL -2
145 /* current version for cover cache */
146 #define CACHE_VERSION 2
147 #define CONFIG_VERSION 1
148 #define CONFIG_FILE "pictureflow.cfg"
150 /** structs we use */
152 struct slide_data {
153 int slide_index;
154 int angle;
155 PFreal cx;
156 PFreal cy;
157 PFreal distance;
160 struct slide_cache {
161 int index; /* index of the cached slide */
162 int hid; /* handle ID of the cached slide */
163 short next; /* "next" slide, with LRU last */
164 short prev; /* "previous" slide */
167 struct album_data {
168 int name_idx;
169 long seek;
172 struct track_data {
173 int name_idx;
174 long seek;
177 struct rect {
178 int left;
179 int right;
180 int top;
181 int bottom;
184 struct load_slide_event_data {
185 int slide_index;
186 int cache_index;
190 struct pfraw_header {
191 int32_t width; /* bmap width in pixels */
192 int32_t height; /* bmap height in pixels */
195 const struct picture logos[]={
196 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
199 enum show_album_name_values { album_name_hide = 0, album_name_bottom,
200 album_name_top };
201 static char* show_album_name_conf[] =
203 "hide",
204 "bottom",
205 "top"
208 #define MAX_SPACING 40
209 #define MAX_MARGIN 80
211 /* config values and their defaults */
212 static int slide_spacing = DISPLAY_WIDTH / 4;
213 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
214 static int num_slides = 4;
215 static int zoom = 100;
216 static bool show_fps = false;
217 static bool resize = true;
218 static int cache_version = 0;
219 static int show_album_name = (LCD_HEIGHT > 100)
220 ? album_name_top : album_name_bottom;
222 static struct configdata config[] =
224 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
225 NULL },
226 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
227 NULL },
228 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
229 NULL },
230 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
231 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
232 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
233 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
234 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
235 show_album_name_conf }
238 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
240 /** below we allocate the memory we want to use **/
242 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
243 static uint8_t reflect_table[REFLECT_HEIGHT];
244 static struct slide_data center_slide;
245 static struct slide_data left_slides[MAX_SLIDES_COUNT];
246 static struct slide_data right_slides[MAX_SLIDES_COUNT];
247 static int slide_frame;
248 static int step;
249 static int target;
250 static int fade;
251 static int center_index = 0; /* index of the slide that is in the center */
252 static int itilt;
253 static PFreal offsetX;
254 static PFreal offsetY;
255 static int number_of_slides;
257 static struct slide_cache cache[SLIDE_CACHE_SIZE];
258 static int cache_free;
259 static int cache_used = -1;
260 static int cache_left_index = -1;
261 static int cache_right_index = -1;
262 static int cache_center_index = -1;
264 /* use long for aligning */
265 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
266 /* queue (as array) for scheduling load_surface */
268 static int empty_slide_hid;
270 unsigned int thread_id;
271 struct event_queue thread_q;
273 static struct tagcache_search tcs;
275 static struct buflib_context buf_ctx;
277 static struct album_data *album;
278 static char *album_names;
279 static int album_count;
281 static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
282 static struct track_data tracks[MAX_TRACKS];
283 static int track_count;
284 static int track_index;
285 static int selected_track;
286 static int selected_track_pulse;
287 void reset_track_list(void);
289 void * plugin_buf;
290 size_t plugin_buf_size;
292 static int old_drawmode;
294 static bool thread_is_running;
296 static int cover_animation_keyframe;
297 static int extra_fade;
299 static int albumtxt_x = 0;
300 static int albumtxt_dir = -1;
301 static int prev_center_index = -1;
303 static int start_index_track_list = 0;
304 static int track_list_visible_entries = 0;
305 static int track_list_y;
306 static int track_list_h;
307 static int track_scroll_index = 0;
308 static int track_scroll_dir = 1;
311 Proposals for transitions:
313 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
314 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
316 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
318 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
320 TODO:
321 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
322 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
324 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
326 enum pf_states {
327 pf_idle = 0,
328 pf_scrolling,
329 pf_cover_in,
330 pf_show_tracks,
331 pf_cover_out
334 static int pf_state;
336 /** code */
337 static inline pix_t fade_color(pix_t c, unsigned int a);
338 bool save_pfraw(char* filename, struct bitmap *bm);
339 bool load_new_slide(void);
340 int load_surface(int);
342 static inline PFreal fmul(PFreal a, PFreal b)
344 return (a*b) >> PFREAL_SHIFT;
348 * This version preshifts each operand, which is useful when we know how many
349 * of the least significant bits will be empty, or are worried about overflow
350 * in a particular calculation
352 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
354 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
357 /* ARMv5+ has a clz instruction equivalent to our function.
359 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
360 static inline int clz(uint32_t v)
362 return __builtin_clz(v);
365 /* Otherwise, use our clz, which can be inlined */
366 #elif defined(CPU_COLDFIRE)
367 /* This clz is based on the log2(n) implementation at
368 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
369 * A clz benchmark plugin showed this to be about 14% faster on coldfire
370 * than the LUT-based version.
372 static inline int clz(uint32_t v)
374 int r = 32;
375 if (v >= 0x10000)
377 v >>= 16;
378 r -= 16;
380 if (v & 0xff00)
382 v >>= 8;
383 r -= 8;
385 if (v & 0xf0)
387 v >>= 4;
388 r -= 4;
390 if (v & 0xc)
392 v >>= 2;
393 r -= 2;
395 if (v & 2)
397 v >>= 1;
398 r -= 1;
400 r -= v;
401 return r;
403 #else
404 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
405 0, 0, 0, 0, 0, 0, 0, 0 };
406 /* This clz is based on the log2(n) implementation at
407 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
408 * It is not any faster than the one above, but trades 16B in the lookup table
409 * for a savings of 12B per each inlined call.
411 static inline int clz(uint32_t v)
413 int r = 28;
414 if (v >= 0x10000)
416 v >>= 16;
417 r -= 16;
419 if (v & 0xff00)
421 v >>= 8;
422 r -= 8;
424 if (v & 0xf0)
426 v >>= 4;
427 r -= 4;
429 return r + clz_lut[v];
431 #endif
433 /* Return the maximum possible left shift for a signed int32, without
434 * overflow
436 static inline int allowed_shift(int32_t val)
438 uint32_t uval = val ^ (val >> 31);
439 return clz(uval) - 1;
442 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
443 * num and den before dividing.
445 static inline PFreal fdiv(PFreal num, PFreal den)
447 int shift = allowed_shift(num);
448 shift = MIN(PFREAL_SHIFT, shift);
449 num <<= shift;
450 den >>= PFREAL_SHIFT - shift;
451 return num / den;
454 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
455 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
456 #define fabs(a) (a < 0 ? -a : a)
457 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
460 #if 0
461 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
462 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
464 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
465 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
467 static inline PFreal fmul(PFreal a, PFreal b)
469 return (a*b) >> PFREAL_SHIFT;
472 static inline PFreal fdiv(PFreal n, PFreal m)
474 return (n<<(PFREAL_SHIFT))/m;
476 #endif
478 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
479 static const short sin_tab[] = {
480 0, 100, 200, 297, 392, 483, 569, 650,
481 724, 792, 851, 903, 946, 980, 1004, 1019,
482 1024, 1019, 1004, 980, 946, 903, 851, 792,
483 724, 650, 569, 483, 392, 297, 200, 100,
484 0, -100, -200, -297, -392, -483, -569, -650,
485 -724, -792, -851, -903, -946, -980, -1004, -1019,
486 -1024, -1019, -1004, -980, -946, -903, -851, -792,
487 -724, -650, -569, -483, -392, -297, -200, -100,
491 static inline PFreal fsin(int iangle)
493 while(iangle < 0)
494 iangle += IANGLE_MAX;
495 iangle &= IANGLE_MASK;
497 int i = (iangle >> 4);
498 PFreal p = sin_tab[i];
499 PFreal q = sin_tab[(i+1)];
500 PFreal g = (q - p);
501 return p + g * (iangle-i*16)/16;
504 static inline PFreal fcos(int iangle)
506 return fsin(iangle + (IANGLE_MAX >> 2));
509 static inline uint32_t div255(uint32_t val)
511 return ((((val >> 8) + val) >> 8) + val) >> 8;
514 #define SCALE_VAL(val,out) div255((val) * (out) + 127)
516 static void output_row_transposed(uint32_t row, void * row_in,
517 struct scaler_context *ctx)
519 pix_t *dest = (pix_t*)ctx->bm->data + row;
520 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
521 #ifdef USEGSLIB
522 uint32_t *qp = (uint32_t*)row_in;
523 for (; dest < end; dest += ctx->bm->height)
524 *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor);
525 #else
526 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
527 uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31),
528 rb_rnd = SCALE_VAL(ctx->round, 31),
529 g_mul = SCALE_VAL(ctx->divisor, 63),
530 g_rnd = SCALE_VAL(ctx->round, 63);
531 int r, g, b;
532 for (; dest < end; dest += ctx->bm->height)
534 r = SC_MUL(qp->r + rb_rnd, rb_mul);
535 g = SC_MUL(qp->g + g_rnd, g_mul);
536 b = SC_MUL(qp->b + rb_rnd, rb_mul);
537 qp++;
538 *dest = LCD_RGBPACK_LCD(r,g,b);
540 #endif
543 static unsigned int get_size(struct bitmap *bm)
545 return bm->width * bm->height * sizeof(pix_t);
548 const struct custom_format format_transposed = {
549 .output_row = output_row_transposed,
550 .get_size = get_size
553 /* Create the lookup table with the scaling values for the reflections */
554 void init_reflect_table(void)
556 int i;
557 for (i = 0; i < REFLECT_HEIGHT; i++)
558 reflect_table[i] =
559 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
560 (5 * REFLECT_HEIGHT);
564 Create an index of all albums from the database.
565 Also store the album names so we can access them later.
567 int create_album_index(void)
569 plugin_buf_size -= UNIQBUF_SIZE * sizeof(long);
570 long *uniqbuf = (long *)(plugin_buf_size + (char *)plugin_buf);
571 album = ((struct album_data *)uniqbuf) - 1;
572 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
573 album_count = 0;
574 rb->tagcache_search(&tcs, tag_album);
575 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
576 unsigned int l, old_l = 0;
577 album_names = plugin_buf;
578 album[0].name_idx = 0;
579 while (rb->tagcache_get_next(&tcs))
581 plugin_buf_size -= sizeof(struct album_data);
582 l = rb->strlen(tcs.result) + 1;
583 if ( album_count > 0 )
584 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
586 if ( l > plugin_buf_size )
587 /* not enough memory */
588 return ERROR_BUFFER_FULL;
590 rb->strcpy(plugin_buf, tcs.result);
591 plugin_buf_size -= l;
592 plugin_buf = l + (char *)plugin_buf;
593 album[-album_count].seek = tcs.result_seek;
594 old_l = l;
595 album_count++;
597 rb->tagcache_search_finish(&tcs);
598 ALIGN_BUFFER(plugin_buf, plugin_buf_size, 4);
599 int i;
600 struct album_data* tmp_album = (struct album_data*)plugin_buf;
601 for (i = album_count - 1; i >= 0; i--)
602 tmp_album[i] = album[-i];
603 album = tmp_album;
604 plugin_buf = album + album_count;
605 plugin_buf_size += UNIQBUF_SIZE * sizeof(long);
606 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
610 Return a pointer to the album name of the given slide_index
612 char* get_album_name(const int slide_index)
614 return album_names + album[slide_index].name_idx;
618 Return a pointer to the track name of the active album
619 create_track_index has to be called first.
621 char* get_track_name(const int track_index)
623 if ( track_index < track_count )
624 return track_names + tracks[track_index].name_idx;
625 return 0;
629 Create the track index of the given slide_index.
631 int create_track_index(const int slide_index)
633 if ( slide_index == track_index ) {
634 return -1;
637 if (!rb->tagcache_search(&tcs, tag_title))
638 return -1;
640 int ret = 0;
641 char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4];
642 int temp_seeks[MAX_TRACKS];
644 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
645 track_count=0;
646 int string_index = 0;
647 int l, track_num, heighest_index = 0;
649 for(l=0;l<MAX_TRACKS;l++)
650 temp_titles[l][0] = '\0';
651 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
653 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
654 if (track_num >= 0)
656 rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]),
657 "%d: %s", track_num+1, tcs.result);
658 temp_seeks[track_num] = tcs.result_seek;
660 else
662 track_num = 0;
663 while (temp_titles[track_num][0] != '\0')
664 track_num++;
665 rb->strcpy(temp_titles[track_num], tcs.result);
666 temp_seeks[track_num] = tcs.result_seek;
668 if (track_num > heighest_index)
669 heighest_index = track_num;
670 track_count++;
673 rb->tagcache_search_finish(&tcs);
674 track_index = slide_index;
676 /* now fix the track list order */
677 l = 0;
678 track_count = 0;
679 while (l <= heighest_index &&
680 string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH)
682 if (temp_titles[l][0] != '\0')
684 rb->strcpy(track_names + string_index, temp_titles[l]);
685 tracks[track_count].name_idx = string_index;
686 tracks[track_count].seek = temp_seeks[l];
687 string_index += rb->strlen(temp_titles[l]) + 1;
688 track_count++;
690 l++;
692 if (ret != 0)
693 return ret;
694 else
695 return (track_count > 0) ? 0 : -1;
699 Determine filename of the album art for the given slide_index and
700 store the result in buf.
701 The algorithm looks for the first track of the given album uses
702 find_albumart to find the filename.
704 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
705 int buflen)
707 if ( slide_index == -1 )
709 rb->strncpy( buf, EMPTY_SLIDE, buflen );
712 if (!rb->tagcache_search(&tcs, tag_filename))
713 return false;
715 bool result;
716 /* find the first track of the album */
717 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
719 if ( rb->tagcache_get_next(&tcs) ) {
720 struct mp3entry id3;
721 int fd;
723 fd = rb->open(tcs.result, O_RDONLY);
724 rb->get_metadata(&id3, fd, tcs.result);
725 rb->close(fd);
726 if ( search_albumart_files(&id3, "", buf, buflen) )
727 result = true;
728 else
729 result = false;
731 else {
732 /* did not find a matching track */
733 result = false;
735 rb->tagcache_search_finish(&tcs);
736 return result;
740 Draw the PictureFlow logo
742 void draw_splashscreen(void)
744 struct screen* display = rb->screens[0];
746 #if LCD_DEPTH > 1
747 rb->lcd_set_background(N_BRIGHT(0));
748 rb->lcd_set_foreground(N_BRIGHT(255));
749 #endif
750 rb->lcd_clear_display();
752 const struct picture* logo = &(logos[display->screen_type]);
753 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
755 rb->lcd_update();
760 Draw a simple progress bar
762 void draw_progressbar(int step)
764 int txt_w, txt_h;
765 const int bar_height = 22;
766 const int w = LCD_WIDTH - 20;
767 const int x = 10;
769 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
771 int y = (LCD_HEIGHT - txt_h)/2;
773 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
774 y += (txt_h + 5);
776 #if LCD_DEPTH > 1
777 rb->lcd_set_foreground(N_BRIGHT(100));
778 #endif
779 rb->lcd_drawrect(x, y, w+2, bar_height);
780 #if LCD_DEPTH > 1
781 rb->lcd_set_foreground(N_PIX(165, 231, 82));
782 #endif
784 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
785 #if LCD_DEPTH > 1
786 rb->lcd_set_foreground(N_BRIGHT(255));
787 #endif
788 rb->lcd_update();
789 rb->yield();
793 Precomupte the album art images and store them in CACHE_PREFIX.
795 bool create_albumart_cache(void)
797 int ret;
799 int i, slides = 0;
800 struct bitmap input_bmp;
802 char pfraw_file[MAX_PATH];
803 char albumart_file[MAX_PATH];
804 unsigned int format = FORMAT_NATIVE;
805 cache_version = 0;
806 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
807 if (resize)
808 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
809 for (i=0; i < album_count; i++)
811 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
813 /* delete existing cache, so it's a true rebuild */
814 if(rb->file_exists(pfraw_file))
815 rb->remove(pfraw_file);
816 draw_progressbar(i);
817 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
818 continue;
820 input_bmp.data = plugin_buf;
821 input_bmp.width = DISPLAY_WIDTH;
822 input_bmp.height = DISPLAY_HEIGHT;
823 ret = scaled_read_bmp_file(albumart_file, &input_bmp,
824 plugin_buf_size, format, &format_transposed);
825 if (ret <= 0) {
826 rb->splash(HZ, "Could not read bmp");
827 continue; /* skip missing/broken files */
829 if (!save_pfraw(pfraw_file, &input_bmp))
831 rb->splash(HZ, "Could not write bmp");
833 slides++;
834 if ( rb->button_get(false) == PICTUREFLOW_MENU ) return false;
836 if ( slides == 0 ) {
837 /* Warn the user that we couldn't find any albumart */
838 rb->splash(2*HZ, "No album art found");
839 return false;
841 return true;
845 Thread used for loading and preparing bitmaps in the background
847 void thread(void)
849 long sleep_time = 5 * HZ;
850 struct queue_event ev;
851 while (1) {
852 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
853 switch (ev.id) {
854 case EV_EXIT:
855 return;
856 case EV_WAKEUP:
857 /* we just woke up */
858 break;
860 while ( load_new_slide() ) {
861 rb->yield();
862 switch (ev.id) {
863 case EV_EXIT:
864 return;
872 End the thread by posting the EV_EXIT event
874 void end_pf_thread(void)
876 if ( thread_is_running ) {
877 rb->queue_post(&thread_q, EV_EXIT, 0);
878 rb->thread_wait(thread_id);
879 /* remove the thread's queue from the broadcast list */
880 rb->queue_delete(&thread_q);
881 thread_is_running = false;
888 Create the thread an setup the event queue
890 bool create_pf_thread(void)
892 /* put the thread's queue in the bcast list */
893 rb->queue_init(&thread_q, true);
894 if ((thread_id = rb->create_thread(
895 thread,
896 thread_stack,
897 sizeof(thread_stack),
899 "Picture load thread"
900 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
901 PRIORITY_REALTIME + 1))
902 IF_COP(, CPU)
904 ) == 0) {
905 return false;
907 thread_is_running = true;
908 rb->queue_post(&thread_q, EV_WAKEUP, 0);
909 return true;
913 Safe the given bitmap as filename in the pfraw format
915 bool save_pfraw(char* filename, struct bitmap *bm)
917 struct pfraw_header bmph;
918 bmph.width = bm->width;
919 bmph.height = bm->height;
920 int fh = rb->creat( filename );
921 if( fh < 0 ) return false;
922 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
923 int y;
924 for( y = 0; y < bm->height; y++ )
926 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
927 rb->write( fh, d, sizeof( pix_t ) * bm->width );
929 rb->close( fh );
930 return true;
935 * The following functions implement the linked-list-in-array used to manage
936 * the LRU cache of slides, and the list of free cache slots.
939 #define seek_right_while(start, cond) \
940 ({ \
941 int ind_, next_ = (start); \
942 do { \
943 ind_ = next_; \
944 next_ = cache[ind_].next; \
945 } while (next_ != cache_used && (cond)); \
946 ind_; \
949 #define seek_left_while(start, cond) \
950 ({ \
951 int ind_, next_ = (start); \
952 do { \
953 ind_ = next_; \
954 next_ = cache[ind_].prev; \
955 } while (ind_ != cache_used && (cond)); \
956 ind_; \
960 Pop the given item from the linked list starting at *head, returning the next
961 item, or -1 if the list is now empty.
963 static inline int lla_pop_item (int *head, int i)
965 int prev = cache[i].prev;
966 int next = cache[i].next;
967 if (i == next)
969 *head = -1;
970 return -1;
972 else if (i == *head)
973 *head = next;
974 cache[next].prev = prev;
975 cache[prev].next = next;
976 return next;
981 Pop the head item from the list starting at *head, returning the index of the
982 item, or -1 if the list is already empty.
984 static inline int lla_pop_head (int *head)
986 int i = *head;
987 if (i != -1)
988 lla_pop_item(head, i);
989 return i;
993 Insert the item at index i before the one at index p.
995 static inline void lla_insert (int i, int p)
997 int next = p;
998 int prev = cache[next].prev;
999 cache[next].prev = i;
1000 cache[prev].next = i;
1001 cache[i].next = next;
1002 cache[i].prev = prev;
1007 Insert the item at index i at the end of the list starting at *head.
1009 static inline void lla_insert_tail (int *head, int i)
1011 if (*head == -1)
1013 *head = i;
1014 cache[i].next = i;
1015 cache[i].prev = i;
1016 } else
1017 lla_insert(i, *head);
1021 Insert the item at index i before the one at index p.
1023 static inline void lla_insert_after(int i, int p)
1025 p = cache[p].next;
1026 lla_insert(i, p);
1031 Insert the item at index i before the one at index p in the list starting at
1032 *head
1034 static inline void lla_insert_before(int *head, int i, int p)
1036 lla_insert(i, p);
1037 if (*head == p)
1038 *head = i;
1043 Free the used slide at index i, and its buffer, and move it to the free
1044 slides list.
1046 static inline void free_slide(int i)
1048 if (cache[i].hid != empty_slide_hid)
1049 buflib_free(&buf_ctx, cache[i].hid);
1050 cache[i].index = -1;
1051 lla_pop_item(&cache_used, i);
1052 lla_insert_tail(&cache_free, i);
1053 if (cache_used == -1)
1055 cache_right_index = -1;
1056 cache_left_index = -1;
1057 cache_center_index = -1;
1063 Free one slide ranked above the given priority. If no such slide can be found,
1064 return false.
1066 static inline int free_slide_prio(int prio)
1068 if (cache_used == -1)
1069 return false;
1070 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1071 int prio_l = cache[l].index < center_index ?
1072 center_index - cache[l].index : 0;
1073 int prio_r = cache[r].index > center_index ?
1074 cache[r].index - center_index : 0;
1075 if (prio_l > prio_r)
1077 i = l;
1078 prio_max = prio_l;
1079 } else {
1080 i = r;
1081 prio_max = prio_r;
1083 if (prio_max > prio)
1085 if (i == cache_left_index)
1086 cache_left_index = cache[i].next;
1087 if (i == cache_right_index)
1088 cache_right_index = cache[i].prev;
1089 free_slide(i);
1090 return true;
1091 } else
1092 return false;
1096 Read the pfraw image given as filename and return the hid of the buffer
1098 int read_pfraw(char* filename, int prio)
1100 struct pfraw_header bmph;
1101 int fh = rb->open(filename, O_RDONLY);
1102 if( fh < 0 )
1103 return empty_slide_hid;
1104 else
1105 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1107 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1108 bmph.width * bmph.height;
1110 int hid;
1111 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1113 if (!hid) {
1114 rb->close( fh );
1115 return 0;
1118 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1120 bm->width = bmph.width;
1121 bm->height = bmph.height;
1122 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1124 int y;
1125 for( y = 0; y < bm->height; y++ )
1127 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1128 data += bm->width;
1130 rb->close( fh );
1131 return hid;
1136 Load the surface for the given slide_index into the cache at cache_index.
1138 static inline bool load_and_prepare_surface(const int slide_index,
1139 const int cache_index,
1140 const int prio)
1142 char tmp_path_name[MAX_PATH+1];
1143 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1144 slide_index);
1146 int hid = read_pfraw(tmp_path_name, prio);
1147 if (!hid)
1148 return false;
1150 cache[cache_index].hid = hid;
1152 if ( cache_index < SLIDE_CACHE_SIZE ) {
1153 cache[cache_index].index = slide_index;
1156 return true;
1161 Load the "next" slide that we can load, freeing old slides if needed, provided
1162 that they are further from center_index than the current slide
1164 bool load_new_slide(void)
1166 int i = -1;
1167 if (cache_center_index != -1)
1169 int next, prev;
1170 if (cache[cache_center_index].index != center_index)
1172 if (cache[cache_center_index].index < center_index)
1174 cache_center_index = seek_right_while(cache_center_index,
1175 cache[next_].index <= center_index);
1176 prev = cache_center_index;
1177 next = cache[cache_center_index].next;
1179 else
1181 cache_center_index = seek_left_while(cache_center_index,
1182 cache[next_].index >= center_index);
1183 next = cache_center_index;
1184 prev = cache[cache_center_index].prev;
1186 if (cache[cache_center_index].index != center_index)
1188 if (cache_free == -1)
1189 free_slide_prio(0);
1190 i = lla_pop_head(&cache_free);
1191 if (!load_and_prepare_surface(center_index, i, 0))
1192 goto fail_and_refree;
1193 if (cache[next].index == -1)
1195 if (cache[prev].index == -1)
1196 goto insert_first_slide;
1197 else
1198 next = cache[prev].next;
1200 lla_insert(i, next);
1201 if (cache[i].index < cache[cache_used].index)
1202 cache_used = i;
1203 cache_center_index = i;
1204 cache_left_index = i;
1205 cache_right_index = i;
1206 return true;
1209 if (cache[cache_left_index].index >
1210 cache[cache_center_index].index)
1211 cache_left_index = cache_center_index;
1212 if (cache[cache_right_index].index <
1213 cache[cache_center_index].index)
1214 cache_right_index = cache_center_index;
1215 cache_left_index = seek_left_while(cache_left_index,
1216 cache[ind_].index - 1 == cache[next_].index);
1217 cache_right_index = seek_right_while(cache_right_index,
1218 cache[ind_].index - 1 == cache[next_].index);
1219 int prio_l = cache[cache_center_index].index -
1220 cache[cache_left_index].index + 1;
1221 int prio_r = cache[cache_right_index].index -
1222 cache[cache_center_index].index + 1;
1223 if ((prio_l < prio_r ||
1224 cache[cache_right_index].index >= number_of_slides) &&
1225 cache[cache_left_index].index > 0)
1227 if (cache_free == -1 && !free_slide_prio(prio_l))
1228 return false;
1229 i = lla_pop_head(&cache_free);
1230 if (load_and_prepare_surface(cache[cache_left_index].index
1231 - 1, i, prio_l))
1233 lla_insert_before(&cache_used, i, cache_left_index);
1234 cache_left_index = i;
1235 return true;
1237 } else if(cache[cache_right_index].index < number_of_slides - 1)
1239 if (cache_free == -1 && !free_slide_prio(prio_r))
1240 return false;
1241 i = lla_pop_head(&cache_free);
1242 if (load_and_prepare_surface(cache[cache_right_index].index
1243 + 1, i, prio_r))
1245 lla_insert_after(i, cache_right_index);
1246 cache_right_index = i;
1247 return true;
1250 } else {
1251 i = lla_pop_head(&cache_free);
1252 if (load_and_prepare_surface(center_index, i, 0))
1254 insert_first_slide:
1255 cache[i].next = i;
1256 cache[i].prev = i;
1257 cache_center_index = i;
1258 cache_left_index = i;
1259 cache_right_index = i;
1260 cache_used = i;
1261 return true;
1264 fail_and_refree:
1265 if (i != -1)
1267 lla_insert_tail(&cache_free, i);
1269 return false;
1274 Get a slide from the buffer
1276 static inline struct dim *get_slide(const int hid)
1278 if (!hid)
1279 return NULL;
1281 struct dim *bmp;
1283 bmp = buflib_get_data(&buf_ctx, hid);
1285 return bmp;
1290 Return the requested surface
1292 static inline struct dim *surface(const int slide_index)
1294 if (slide_index < 0)
1295 return 0;
1296 if (slide_index >= number_of_slides)
1297 return 0;
1298 int i;
1299 if ((i = cache_used ) != -1)
1301 do {
1302 if (cache[i].index == slide_index)
1303 return get_slide(cache[i].hid);
1304 i = cache[i].next;
1305 } while (i != cache_used);
1307 return get_slide(empty_slide_hid);
1311 adjust slides so that they are in "steady state" position
1313 void reset_slides(void)
1315 center_slide.angle = 0;
1316 center_slide.cx = 0;
1317 center_slide.cy = 0;
1318 center_slide.distance = 0;
1319 center_slide.slide_index = center_index;
1321 int i;
1322 for (i = 0; i < num_slides; i++) {
1323 struct slide_data *si = &left_slides[i];
1324 si->angle = itilt;
1325 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1326 si->cy = offsetY;
1327 si->slide_index = center_index - 1 - i;
1328 si->distance = 0;
1331 for (i = 0; i < num_slides; i++) {
1332 struct slide_data *si = &right_slides[i];
1333 si->angle = -itilt;
1334 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1335 si->cy = offsetY;
1336 si->slide_index = center_index + 1 + i;
1337 si->distance = 0;
1343 Updates look-up table and other stuff necessary for the rendering.
1344 Call this when the viewport size or slide dimension is changed.
1346 * To calculate the offset that will provide the proper margin, we use the same
1347 * projection used to render the slides. The solution for xc, the slide center,
1348 * is:
1349 * xp * (zo + xs * sin(r))
1350 * xc = xp - xs * cos(r) + ───────────────────────
1352 * TODO: support moving the side slides toward or away from the camera
1354 void recalc_offsets(void)
1356 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1357 PFreal zo;
1358 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1359 PFREAL_ONE) * zoom / 100;
1360 PFreal cosr, sinr;
1362 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1363 cosr = fcos(-itilt);
1364 sinr = fsin(-itilt);
1365 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1366 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1367 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1368 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1369 / CAM_DIST;
1370 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1375 Fade the given color by spreading the fb_data (ushort)
1376 to an uint, multiply and compress the result back to a ushort.
1378 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1379 static inline pix_t fade_color(pix_t c, unsigned int a)
1381 unsigned int result;
1382 c = swap16(c);
1383 a = (a + 2) & 0x1fc;
1384 result = ((c & 0xf81f) * a) & 0xf81f00;
1385 result |= ((c & 0x7e0) * a) & 0x7e000;
1386 result >>= 8;
1387 return swap16(result);
1389 #elif LCD_PIXELFORMAT == RGB565
1390 static inline pix_t fade_color(pix_t c, unsigned int a)
1392 unsigned int result;
1393 a = (a + 2) & 0x1fc;
1394 result = ((c & 0xf81f) * a) & 0xf81f00;
1395 result |= ((c & 0x7e0) * a) & 0x7e000;
1396 result >>= 8;
1397 return result;
1399 #else
1400 static inline pix_t fade_color(pix_t c, unsigned int a)
1402 return (unsigned int)c * a >> 8;
1404 #endif
1407 * Render a single slide
1408 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1409 * on the slide from its center, zo is the slide's depth offset from the plane
1410 * of the display, r is the angle at which the slide is tilted, and xp is the
1411 * point on the display corresponding to xs on the slide, the projection
1412 * formulas are:
1414 * z * (xc + xs * cos(r))
1415 * xp = ──────────────────────
1416 * z + zo + xs * sin(r)
1418 * z * (xc - xp) - xp * zo
1419 * xs = ────────────────────────
1420 * xp * sin(r) - z * cos(r)
1422 * We use the xp projection once, to find the left edge of the slide on the
1423 * display. From there, we use the xs reverse projection to find the horizontal
1424 * offset from the slide center of each column on the screen, until we reach
1425 * the right edge of the slide, or the screen. The reverse projection can be
1426 * optimized by saving the numerator and denominator of the fraction, which can
1427 * then be incremented by (z + zo) and sin(r) respectively.
1429 void render_slide(struct slide_data *slide, const int alpha)
1431 struct dim *bmp = surface(slide->slide_index);
1432 if (!bmp) {
1433 return;
1435 if (slide->angle > 255 || slide->angle < -255)
1436 return;
1437 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1439 const int sw = bmp->width;
1440 const int sh = bmp->height;
1441 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1443 const int h = LCD_HEIGHT;
1444 const int w = LCD_WIDTH;
1447 PFreal cosr = fcos(slide->angle);
1448 PFreal sinr = fsin(slide->angle);
1449 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1450 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1451 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1452 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1453 (CAM_DIST_R + zo + fmul(xs,sinr)));
1455 /* Since we're finding the screen position of the left edge of the slide,
1456 * we round up.
1458 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1459 >> PFREAL_SHIFT;
1460 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1461 if (xi >= w) {
1462 return;
1464 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1465 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1466 xs = fdiv(xsnum, xsden);
1468 xsnumi = -CAM_DIST_R - zo;
1469 xsdeni = sinr;
1470 int x;
1471 int dy = PFREAL_ONE;
1472 for (x = xi; x < w; x++) {
1473 int column = (xs - slide_left) / PFREAL_ONE;
1474 if (column >= sw)
1475 break;
1476 if (zo || slide->angle)
1477 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1478 int y1 = (LCD_HEIGHT / 2) - 1;
1479 int y2 = y1 + 1;
1480 pix_t *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
1481 pix_t *pixel2 = pixel1 + BUFFER_WIDTH;
1482 const int pixelstep = BUFFER_WIDTH;
1484 int p1 = (bmp->height - 1 - (DISPLAY_OFFS)) * PFREAL_ONE;
1485 int p2 = p1 + dy;
1486 const pix_t *ptr = &src[column * bmp->height];
1488 if (alpha == 256)
1490 while ((y1 >= 0) && (p1 >= 0))
1492 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
1493 p1 -= dy;
1494 y1--;
1495 pixel1 -= pixelstep;
1497 while ((p2 < sh * PFREAL_ONE) && (y2 < h))
1499 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
1500 p2 += dy;
1501 y2++;
1502 pixel2 += pixelstep;
1504 while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
1505 (y2 < h))
1507 int ty = (p2 >> PFREAL_SHIFT) - sh;
1508 int lalpha = reflect_table[ty];
1509 *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
1510 p2 += dy;
1511 y2++;
1512 pixel2 += pixelstep;
1515 else
1517 while ((y1 >= 0) && (p1 >= 0))
1519 *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT],alpha);
1520 p1 -= dy;
1521 y1--;
1522 pixel1 -= pixelstep;
1524 while ((p2 < sh * PFREAL_ONE) && (y2 < h))
1526 *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT],alpha);
1527 p2 += dy;
1528 y2++;
1529 pixel2 += pixelstep;
1531 while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
1532 (y2 < h))
1534 int ty = (p2 >> PFREAL_SHIFT) - sh;
1535 int lalpha = (reflect_table[ty] * alpha + 128) >> 8;
1536 *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
1537 p2 += dy;
1538 y2++;
1539 pixel2 += pixelstep;
1542 if (zo || slide->angle)
1544 xsnum += xsnumi;
1545 xsden += xsdeni;
1546 xs = fdiv(xsnum, xsden);
1547 } else
1548 xs += PFREAL_ONE;
1551 /* let the music play... */
1552 rb->yield();
1553 return;
1558 Jump the the given slide_index
1560 static inline void set_current_slide(const int slide_index)
1562 int old_center_index = center_index;
1563 step = 0;
1564 center_index = fbound(slide_index, 0, number_of_slides - 1);
1565 if (old_center_index != center_index)
1566 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1567 target = center_index;
1568 slide_frame = slide_index << 16;
1569 reset_slides();
1573 Start the animation for changing slides
1575 void start_animation(void)
1577 step = (target < center_slide.slide_index) ? -1 : 1;
1578 pf_state = pf_scrolling;
1582 Go to the previous slide
1584 void show_previous_slide(void)
1586 if (step == 0) {
1587 if (center_index > 0) {
1588 target = center_index - 1;
1589 start_animation();
1591 } else if ( step > 0 ) {
1592 target = center_index;
1593 start_animation();
1594 } else {
1595 target = fmax(0, center_index - 2);
1601 Go to the next slide
1603 void show_next_slide(void)
1605 if (step == 0) {
1606 if (center_index < number_of_slides - 1) {
1607 target = center_index + 1;
1608 start_animation();
1610 } else if ( step < 0 ) {
1611 target = center_index;
1612 start_animation();
1613 } else {
1614 target = fmin(center_index + 2, number_of_slides - 1);
1620 Return true if the rect has size 0
1622 static inline bool is_empty_rect(struct rect *r)
1624 return ((r->left == 0) && (r->right == 0) && (r->top == 0)
1625 && (r->bottom == 0));
1630 Render the slides. Updates only the offscreen buffer.
1632 void render_all_slides(void)
1634 MYLCD(set_background)(G_BRIGHT(0));
1635 /* TODO: Optimizes this by e.g. invalidating rects */
1636 MYLCD(clear_display)();
1638 int nleft = num_slides;
1639 int nright = num_slides;
1641 int index;
1642 if (step == 0) {
1643 /* no animation, boring plain rendering */
1644 for (index = nleft - 2; index >= 0; index--) {
1645 int alpha = (index < nleft - 2) ? 256 : 128;
1646 alpha -= extra_fade;
1647 if (alpha > 0 )
1648 render_slide(&left_slides[index], alpha);
1650 for (index = nright - 2; index >= 0; index--) {
1651 int alpha = (index < nright - 2) ? 256 : 128;
1652 alpha -= extra_fade;
1653 if (alpha > 0 )
1654 render_slide(&right_slides[index], alpha);
1656 } else {
1657 /* the first and last slide must fade in/fade out */
1658 for (index = nleft - 1; index >= 0; index--) {
1659 int alpha = 256;
1660 if (index == nleft - 1)
1661 alpha = (step > 0) ? 0 : 128 - fade / 2;
1662 if (index == nleft - 2)
1663 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1664 if (index == nleft - 3)
1665 alpha = (step > 0) ? 256 - fade / 2 : 256;
1666 render_slide(&left_slides[index], alpha);
1668 for (index = nright - 1; index >= 0; index--) {
1669 int alpha = (index < nright - 2) ? 256 : 128;
1670 if (index == nright - 1)
1671 alpha = (step > 0) ? fade / 2 : 0;
1672 if (index == nright - 2)
1673 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1674 if (index == nright - 3)
1675 alpha = (step > 0) ? 256 : 128 + fade / 2;
1676 render_slide(&right_slides[index], alpha);
1679 render_slide(&center_slide, 256);
1684 Updates the animation effect. Call this periodically from a timer.
1686 void update_scroll_animation(void)
1688 if (step == 0)
1689 return;
1691 int speed = 16384;
1692 int i;
1694 /* deaccelerate when approaching the target */
1695 if (true) {
1696 const int max = 2 * 65536;
1698 int fi = slide_frame;
1699 fi -= (target << 16);
1700 if (fi < 0)
1701 fi = -fi;
1702 fi = fmin(fi, max);
1704 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1705 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1708 slide_frame += speed * step;
1710 int index = slide_frame >> 16;
1711 int pos = slide_frame & 0xffff;
1712 int neg = 65536 - pos;
1713 int tick = (step < 0) ? neg : pos;
1714 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1716 /* the leftmost and rightmost slide must fade away */
1717 fade = pos / 256;
1719 if (step < 0)
1720 index++;
1721 if (center_index != index) {
1722 center_index = index;
1723 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1724 slide_frame = index << 16;
1725 center_slide.slide_index = center_index;
1726 for (i = 0; i < num_slides; i++)
1727 left_slides[i].slide_index = center_index - 1 - i;
1728 for (i = 0; i < num_slides; i++)
1729 right_slides[i].slide_index = center_index + 1 + i;
1732 center_slide.angle = (step * tick * itilt) >> 16;
1733 center_slide.cx = -step * fmul(offsetX, ftick);
1734 center_slide.cy = fmul(offsetY, ftick);
1736 if (center_index == target) {
1737 reset_slides();
1738 pf_state = pf_idle;
1739 step = 0;
1740 fade = 256;
1741 return;
1744 for (i = 0; i < num_slides; i++) {
1745 struct slide_data *si = &left_slides[i];
1746 si->angle = itilt;
1747 si->cx =
1748 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1749 * slide_spacing * ftick);
1750 si->cy = offsetY;
1753 for (i = 0; i < num_slides; i++) {
1754 struct slide_data *si = &right_slides[i];
1755 si->angle = -itilt;
1756 si->cx =
1757 offsetX + slide_spacing * i * PFREAL_ONE - step
1758 * slide_spacing * ftick;
1759 si->cy = offsetY;
1762 if (step > 0) {
1763 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1764 right_slides[0].angle = -(neg * itilt) >> 16;
1765 right_slides[0].cx = fmul(offsetX, ftick);
1766 right_slides[0].cy = fmul(offsetY, ftick);
1767 } else {
1768 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1769 left_slides[0].angle = (pos * itilt) >> 16;
1770 left_slides[0].cx = -fmul(offsetX, ftick);
1771 left_slides[0].cy = fmul(offsetY, ftick);
1774 /* must change direction ? */
1775 if (target < index)
1776 if (step > 0)
1777 step = -1;
1778 if (target > index)
1779 if (step < 0)
1780 step = 1;
1785 Cleanup the plugin
1787 void cleanup(void *parameter)
1789 (void) parameter;
1790 /* Turn on backlight timeout (revert to settings) */
1791 backlight_use_settings(); /* backlight control in lib/helper.c */
1793 #ifdef USEGSLIB
1794 grey_release();
1795 #endif
1796 rb->lcd_set_drawmode(old_drawmode);
1800 Create the "?" slide, that is shown while loading
1801 or when no cover was found.
1803 int create_empty_slide(bool force)
1805 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1806 struct bitmap input_bmp;
1807 int ret;
1808 input_bmp.width = DISPLAY_WIDTH;
1809 input_bmp.height = DISPLAY_HEIGHT;
1810 #if LCD_DEPTH > 1
1811 input_bmp.format = FORMAT_NATIVE;
1812 #endif
1813 input_bmp.data = (char*)plugin_buf;
1814 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1815 plugin_buf_size,
1816 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1817 &format_transposed);
1818 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1819 return false;
1822 return true;
1826 Shows the album name setting menu
1828 int album_name_menu(void)
1830 int selection = show_album_name;
1832 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
1833 "Hide album title", "Show at the bottom", "Show at the top");
1834 rb->do_menu(&album_name_menu, &selection, NULL, false);
1836 show_album_name = selection;
1837 return GO_TO_PREVIOUS;
1841 Shows the settings menu
1843 int settings_menu(void)
1845 int selection = 0;
1846 bool old_val;
1848 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1849 "Spacing", "Centre margin", "Number of slides", "Zoom",
1850 "Show album title", "Resize Covers", "Rebuild cache");
1852 do {
1853 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1854 switch(selection) {
1855 case 0:
1856 rb->set_bool("Show FPS", &show_fps);
1857 reset_track_list();
1858 break;
1860 case 1:
1861 rb->set_int("Spacing between slides", "", 1,
1862 &slide_spacing,
1863 NULL, 1, 0, 100, NULL );
1864 recalc_offsets();
1865 reset_slides();
1866 break;
1868 case 2:
1869 rb->set_int("Centre margin", "", 1,
1870 &center_margin,
1871 NULL, 1, 0, 80, NULL );
1872 recalc_offsets();
1873 reset_slides();
1874 break;
1876 case 3:
1877 rb->set_int("Number of slides", "", 1, &num_slides,
1878 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1879 recalc_offsets();
1880 reset_slides();
1881 break;
1883 case 4:
1884 rb->set_int("Zoom", "", 1, &zoom,
1885 NULL, 1, 10, 300, NULL );
1886 recalc_offsets();
1887 reset_slides();
1888 break;
1889 case 5:
1890 album_name_menu();
1891 reset_track_list();
1892 recalc_offsets();
1893 reset_slides();
1894 break;
1895 case 6:
1896 old_val = resize;
1897 rb->set_bool("Resize Covers", &resize);
1898 if (old_val == resize) /* changed? */
1899 break;
1900 /* fallthrough if changed, since cache needs to be rebuilt */
1901 case 7:
1902 cache_version = 0;
1903 rb->remove(EMPTY_SLIDE);
1904 rb->splash(HZ, "Cache will be rebuilt on next restart");
1905 break;
1907 case MENU_ATTACHED_USB:
1908 return PLUGIN_USB_CONNECTED;
1910 } while ( selection >= 0 );
1911 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
1912 return 0;
1916 Show the main menu
1918 int main_menu(void)
1920 int selection = 0;
1921 int result;
1923 #if LCD_DEPTH > 1
1924 rb->lcd_set_foreground(N_BRIGHT(255));
1925 #endif
1927 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
1928 "Settings", "Return", "Quit");
1929 while (1) {
1930 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
1931 case 0:
1932 result = settings_menu();
1933 if ( result != 0 ) return result;
1934 break;
1936 case 1:
1937 return 0;
1939 case 2:
1940 return -1;
1942 case MENU_ATTACHED_USB:
1943 return PLUGIN_USB_CONNECTED;
1945 default:
1946 return 0;
1952 Animation step for zooming into the current cover
1954 void update_cover_in_animation(void)
1956 cover_animation_keyframe++;
1957 if( cover_animation_keyframe < 20 ) {
1958 center_slide.distance-=5;
1959 center_slide.angle+=1;
1960 extra_fade += 13;
1962 else if( cover_animation_keyframe < 35 ) {
1963 center_slide.angle+=16;
1965 else {
1966 cover_animation_keyframe = 0;
1967 pf_state = pf_show_tracks;
1972 Animation step for zooming out the current cover
1974 void update_cover_out_animation(void)
1976 cover_animation_keyframe++;
1977 if( cover_animation_keyframe <= 15 ) {
1978 center_slide.angle-=16;
1980 else if( cover_animation_keyframe < 35 ) {
1981 center_slide.distance+=5;
1982 center_slide.angle-=1;
1983 extra_fade -= 13;
1985 else {
1986 cover_animation_keyframe = 0;
1987 pf_state = pf_idle;
1992 Draw a blue gradient at y with height h
1994 static inline void draw_gradient(int y, int h)
1996 static int r, inc, c;
1997 inc = (100 << 8) / h;
1998 c = 0;
1999 selected_track_pulse = (selected_track_pulse+1) % 10;
2000 int c2 = selected_track_pulse - 5;
2001 for (r=0; r<h; r++) {
2002 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2003 c2+250-(c >> 8)));
2004 MYLCD(hline)(0, LCD_WIDTH, r+y);
2005 if ( r > h/2 )
2006 c-=inc;
2007 else
2008 c+=inc;
2013 static void track_list_yh(int char_height)
2015 switch (show_album_name)
2017 case album_name_hide:
2018 track_list_y = (show_fps ? char_height : 0);
2019 track_list_h = LCD_HEIGHT - track_list_y;
2020 break;
2021 case album_name_bottom:
2022 track_list_y = (show_fps ? char_height : 0);
2023 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2024 break;
2025 default: /* case album_name_top */
2026 track_list_y = char_height * 2;
2027 track_list_h = LCD_HEIGHT - track_list_y -
2028 (show_fps ? char_height : 0);
2029 break;
2034 Reset the track list after a album change
2036 void reset_track_list(void)
2038 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2039 track_list_yh(albumtxt_h);
2040 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2041 start_index_track_list = 0;
2042 track_scroll_index = 0;
2043 track_scroll_dir = 1;
2044 selected_track = 0;
2046 /* let the tracklist start more centered
2047 * if the screen isn't filled with tracks */
2048 if (track_count*albumtxt_h < track_list_h)
2050 track_list_h = track_count * albumtxt_h;
2051 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2056 Display the list of tracks
2058 void show_track_list(void)
2060 MYLCD(clear_display)();
2061 if ( center_slide.slide_index != track_index ) {
2062 create_track_index(center_slide.slide_index);
2063 reset_track_list();
2065 static int titletxt_w, titletxt_x, color, titletxt_h;
2066 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2068 int titletxt_y = track_list_y;
2069 int track_i;
2070 track_i = start_index_track_list;
2071 for (;track_i < track_list_visible_entries+start_index_track_list;
2072 track_i++)
2074 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2075 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2076 if ( track_i == selected_track ) {
2077 draw_gradient(titletxt_y, titletxt_h);
2078 MYLCD(set_foreground)(G_BRIGHT(255));
2079 if (titletxt_w > LCD_WIDTH ) {
2080 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2081 track_scroll_dir = 1;
2082 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2083 track_scroll_index += track_scroll_dir*2;
2084 titletxt_x = track_scroll_index;
2086 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2088 else {
2089 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2090 MYLCD(set_foreground)(G_BRIGHT(color));
2091 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2093 titletxt_y += titletxt_h;
2097 void select_next_track(void)
2099 if ( selected_track < track_count - 1 ) {
2100 selected_track++;
2101 track_scroll_index = 0;
2102 track_scroll_dir = 1;
2103 if (selected_track==(track_list_visible_entries+start_index_track_list))
2104 start_index_track_list++;
2108 void select_prev_track(void)
2110 if (selected_track > 0 ) {
2111 if (selected_track==start_index_track_list) start_index_track_list--;
2112 track_scroll_index = 0;
2113 track_scroll_dir = 1;
2114 selected_track--;
2119 Draw the current album name
2121 void draw_album_text(void)
2123 if (0 == show_album_name)
2124 return;
2126 int albumtxt_w, albumtxt_h;
2127 int albumtxt_y = 0;
2129 char *albumtxt;
2130 int c;
2131 /* Draw album text */
2132 if ( pf_state == pf_scrolling ) {
2133 c = ((slide_frame & 0xffff )/ 255);
2134 if (step < 0) c = 255-c;
2135 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2136 albumtxt = get_album_name(center_index+step);
2137 c = (c-128)*2;
2139 else {
2140 albumtxt = get_album_name(center_index);
2141 c = (128-c)*2;
2144 else {
2145 c= 255;
2146 albumtxt = get_album_name(center_index);
2149 MYLCD(set_foreground)(G_BRIGHT(c));
2150 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2151 if (center_index != prev_center_index) {
2152 albumtxt_x = 0;
2153 albumtxt_dir = -1;
2154 prev_center_index = center_index;
2157 if (show_album_name == album_name_top)
2158 albumtxt_y = albumtxt_h / 2;
2159 else
2160 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2162 if (albumtxt_w > LCD_WIDTH ) {
2163 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2164 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2165 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2166 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2167 albumtxt_x += albumtxt_dir;
2170 else {
2171 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2179 Main function that also contain the main plasma
2180 algorithm.
2182 int main(void)
2184 int ret;
2186 rb->lcd_setfont(FONT_UI);
2187 draw_splashscreen();
2189 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2190 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2191 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2192 return PLUGIN_ERROR;
2196 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2198 init_reflect_table();
2200 ALIGN_BUFFER(plugin_buf, plugin_buf_size, 4);
2201 ret = create_album_index();
2202 if (ret == ERROR_BUFFER_FULL) {
2203 rb->splash(HZ, "Not enough memory for album names");
2204 return PLUGIN_ERROR;
2205 } else if (ret == ERROR_NO_ALBUMS) {
2206 rb->splash(HZ, "No albums found. Please enable database");
2207 return PLUGIN_ERROR;
2210 ALIGN_BUFFER(plugin_buf, plugin_buf_size, 4);
2211 number_of_slides = album_count;
2212 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2213 rb->splash(HZ, "Could not create album art cache");
2214 return PLUGIN_ERROR;
2217 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2218 rb->splash(HZ, "Could not load the empty slide");
2219 return PLUGIN_ERROR;
2221 cache_version = CACHE_VERSION;
2222 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2225 #ifdef USEGSLIB
2226 long grey_buf_used;
2227 if (!grey_init(plugin_buf, plugin_buf_size, GREY_BUFFERED|GREY_ON_COP,
2228 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2230 rb->splash(HZ, "Greylib init failed!");
2231 return PLUGIN_ERROR;
2233 grey_setfont(FONT_UI);
2234 plugin_buf_size -= grey_buf_used;
2235 plugin_buf = (void*)(grey_buf_used + (char*)plugin_buf);
2236 #endif
2237 buflib_init(&buf_ctx, (void *)plugin_buf, plugin_buf_size);
2239 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2241 rb->splash(HZ, "Unable to load empty slide image");
2242 return PLUGIN_ERROR;
2245 if (!create_pf_thread()) {
2246 rb->splash(HZ, "Cannot create thread!");
2247 return PLUGIN_ERROR;
2250 int i;
2252 /* initialize */
2253 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2254 cache[i].hid = 0;
2255 cache[i].index = 0;
2256 cache[i].next = i + 1;
2257 cache[i].prev = i - 1;
2259 cache[0].prev = i - 1;
2260 cache[i - 1].next = 0;
2261 cache_free = 0;
2262 buffer = LCD_BUF;
2264 pf_state = pf_idle;
2266 track_index = -1;
2267 extra_fade = 0;
2268 slide_frame = 0;
2269 step = 0;
2270 target = 0;
2271 fade = 256;
2273 recalc_offsets();
2274 reset_slides();
2276 char fpstxt[10];
2277 int button;
2279 int frames = 0;
2280 long last_update = *rb->current_tick;
2281 long current_update;
2282 long update_interval = 100;
2283 int fps = 0;
2284 int fpstxt_y;
2286 bool instant_update;
2287 old_drawmode = rb->lcd_get_drawmode();
2288 #ifdef USEGSLIB
2289 grey_show(true);
2290 grey_set_drawmode(DRMODE_FG);
2291 #endif
2292 rb->lcd_set_drawmode(DRMODE_FG);
2293 while (true) {
2294 current_update = *rb->current_tick;
2295 frames++;
2297 /* Initial rendering */
2298 instant_update = false;
2300 /* Handle states */
2301 switch ( pf_state ) {
2302 case pf_scrolling:
2303 update_scroll_animation();
2304 render_all_slides();
2305 instant_update = true;
2306 break;
2307 case pf_cover_in:
2308 update_cover_in_animation();
2309 render_all_slides();
2310 instant_update = true;
2311 break;
2312 case pf_cover_out:
2313 update_cover_out_animation();
2314 render_all_slides();
2315 instant_update = true;
2316 break;
2317 case pf_show_tracks:
2318 show_track_list();
2319 break;
2320 case pf_idle:
2321 render_all_slides();
2322 break;
2325 /* Calculate FPS */
2326 if (current_update - last_update > update_interval) {
2327 fps = frames * HZ / (current_update - last_update);
2328 last_update = current_update;
2329 frames = 0;
2331 /* Draw FPS */
2332 if (show_fps)
2334 #ifdef USEGSLIB
2335 MYLCD(set_foreground)(G_BRIGHT(255));
2336 #else
2337 MYLCD(set_foreground)(G_PIX(255,0,0));
2338 #endif
2339 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2340 if (show_album_name == album_name_top)
2341 fpstxt_y = LCD_HEIGHT -
2342 rb->screens[SCREEN_MAIN]->getcharheight();
2343 else
2344 fpstxt_y = 0;
2345 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2347 draw_album_text();
2350 /* Copy offscreen buffer to LCD and give time to other threads */
2351 MYLCD(update)();
2352 rb->yield();
2354 /*/ Handle buttons */
2355 button = pluginlib_getaction(instant_update ? 0 : HZ/16,
2356 plugin_contexts, NB_ACTION_CONTEXTS);
2358 switch (button) {
2359 case PICTUREFLOW_QUIT:
2360 return PLUGIN_OK;
2362 case PICTUREFLOW_MENU:
2363 if ( pf_state == pf_idle || pf_state == pf_scrolling ) {
2364 #ifdef USEGSLIB
2365 grey_show(false);
2366 #endif
2367 ret = main_menu();
2368 if ( ret == -1 ) return PLUGIN_OK;
2369 if ( ret != 0 ) return i;
2370 #ifdef USEGSLIB
2371 grey_show(true);
2372 #endif
2373 MYLCD(set_drawmode)(DRMODE_FG);
2375 else {
2376 pf_state = pf_cover_out;
2378 break;
2380 case PICTUREFLOW_NEXT_ALBUM:
2381 case PICTUREFLOW_NEXT_ALBUM_REPEAT:
2382 #ifdef HAVE_SCROLLWHEEL
2383 if ( pf_state == pf_show_tracks )
2384 select_next_track();
2385 #endif
2386 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2387 show_next_slide();
2388 break;
2390 case PICTUREFLOW_PREV_ALBUM:
2391 case PICTUREFLOW_PREV_ALBUM_REPEAT:
2392 #ifdef HAVE_SCROLLWHEEL
2393 if ( pf_state == pf_show_tracks )
2394 select_prev_track();
2395 #endif
2396 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2397 show_previous_slide();
2398 break;
2400 #ifndef HAVE_SCROLLWHEEL
2401 case PICTUREFLOW_NEXT_TRACK:
2402 case PICTUREFLOW_NEXT_TRACK_REPEAT:
2403 if ( pf_state == pf_show_tracks )
2404 select_next_track();
2405 break;
2407 case PICTUREFLOW_PREV_TRACK:
2408 case PICTUREFLOW_PREV_TRACK_REPEAT:
2409 if ( pf_state == pf_show_tracks )
2410 select_prev_track();
2411 break;
2412 #endif
2414 case PICTUREFLOW_SELECT_ALBUM:
2415 if ( pf_state == pf_idle ) {
2416 pf_state = pf_cover_in;
2418 if ( pf_state == pf_show_tracks )
2419 pf_state = pf_cover_out;
2420 break;
2422 default:
2423 if (rb->default_event_handler_ex(button, cleanup, NULL)
2424 == SYS_USB_CONNECTED)
2425 return PLUGIN_USB_CONNECTED;
2426 break;
2433 /*************************** Plugin entry point ****************************/
2435 enum plugin_status plugin_start(const void *parameter)
2437 int ret;
2438 (void) parameter;
2439 #if LCD_DEPTH > 1
2440 rb->lcd_set_backdrop(NULL);
2441 #endif
2442 /* Turn off backlight timeout */
2443 backlight_force_on(); /* backlight control in lib/helper.c */
2444 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2445 rb->cpu_boost(true);
2446 #endif
2447 plugin_buf = rb->plugin_get_buffer(&plugin_buf_size);
2448 ret = main();
2449 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2450 rb->cpu_boost(false);
2451 #endif
2452 if ( ret == PLUGIN_OK ) {
2453 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2454 CONFIG_VERSION))
2456 rb->splash(HZ, "Error writing config.");
2457 ret = PLUGIN_ERROR;
2461 end_pf_thread();
2462 cleanup(NULL);
2463 return ret;