Rename variables sectorbuf and verbose to avoid clashes in rbutil. Cleanup exports...
[Rockbox.git] / apps / plugins / pictureflow.c
blobbea028eadc453e7bcf0bac44fde528d3eb0b84ab
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 * All files in this archive are subject to the GNU General Public License.
17 * See the file COPYING in the source tree root for full license agreement.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ****************************************************************************/
24 #include "plugin.h"
25 #include "pluginlib_actions.h"
26 #include "helper.h"
27 #include "bmp.h"
28 #include "picture.h"
29 #include "pictureflow_logo.h"
30 #include "pictureflow_emptyslide.h"
33 PLUGIN_HEADER
35 /******************************* Globals ***********************************/
37 static const struct plugin_api *rb; /* global api struct pointer */
39 const struct button_mapping *plugin_contexts[]
40 = {generic_actions, generic_directions};
42 #define NB_ACTION_CONTEXTS sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
44 /* Key assignement */
45 #if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
46 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
47 || (CONFIG_KEYPAD == IPOD_4G_PAD) \
48 || (CONFIG_KEYPAD == SANSA_E200_PAD)
49 #define SCROLLWHEEL
50 #endif
52 #ifdef SCROLLWHEEL
53 #define PICTUREFLOW_NEXT_ALBUM PLA_DOWN
54 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_DOWN_REPEAT
55 #define PICTUREFLOW_PREV_ALBUM PLA_UP
56 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_UP_REPEAT
57 #else
58 #define PICTUREFLOW_NEXT_ALBUM PLA_RIGHT
59 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_RIGHT_REPEAT
60 #define PICTUREFLOW_PREV_ALBUM PLA_LEFT
61 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_LEFT_REPEAT
62 #define PICTUREFLOW_NEXT_TRACK PLA_DOWN
63 #define PICTUREFLOW_NEXT_TRACK_REPEAT PLA_DOWN_REPEAT
64 #define PICTUREFLOW_PREV_TRACK PLA_UP
65 #define PICTUREFLOW_PREV_TRACK_REPEAT PLA_UP_REPEAT
66 #endif
67 #define PICTUREFLOW_MENU PLA_MENU
68 #define PICTUREFLOW_QUIT PLA_QUIT
69 #define PICTUREFLOW_SELECT_ALBUM PLA_FIRE
72 /* for fixed-point arithmetic, we need minimum 32-bit long
73 long long (64-bit) might be useful for multiplication and division */
74 #define PFreal long
75 #define PFREAL_SHIFT 10
76 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
77 #define PFREAL_ONE (1 << PFREAL_SHIFT)
78 #define PFREAL_HALF (PFREAL_ONE >> 1)
81 #define IANGLE_MAX 1024
82 #define IANGLE_MASK 1023
84 /* maximum size of an slide */
85 #define MAX_IMG_WIDTH LCD_WIDTH
86 #define MAX_IMG_HEIGHT LCD_HEIGHT
88 #if (LCD_HEIGHT < 100)
89 #define PREFERRED_IMG_WIDTH 50
90 #define PREFERRED_IMG_HEIGHT 50
91 #else
92 #define PREFERRED_IMG_WIDTH 100
93 #define PREFERRED_IMG_HEIGHT 100
94 #endif
96 #define BUFFER_WIDTH LCD_WIDTH
97 #define BUFFER_HEIGHT LCD_HEIGHT
99 #define SLIDE_CACHE_SIZE 100
101 #define MAX_SLIDES_COUNT 10
103 #define SPACING_BETWEEN_SLIDE 40
104 #define EXTRA_SPACING_FOR_CENTER_SLIDE 0
106 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
107 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
109 #define EV_EXIT 9999
110 #define EV_WAKEUP 1337
112 /* maximum number of albums */
113 #define MAX_ALBUMS 1024
114 #define AVG_ALBUM_NAME_LENGTH 20
116 #define MAX_TRACKS 50
117 #define AVG_TRACK_NAME_LENGTH 20
120 #define UNIQBUF_SIZE (64*1024)
122 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
123 #define CONFIG_FILE CACHE_PREFIX "/pictureflow.config"
125 /* Error return values */
126 #define ERROR_NO_ALBUMS -1
127 #define ERROR_BUFFER_FULL -2
130 /** structs we use */
132 struct slide_data {
133 int slide_index;
134 int angle;
135 PFreal cx;
136 PFreal cy;
137 PFreal distance;
140 struct slide_cache {
141 int index; /* index of the cached slide */
142 int hid; /* handle ID of the cached slide */
143 long touched; /* last time the slide was touched */
146 struct album_data {
147 int name_idx;
148 long seek;
151 struct track_data {
152 int name_idx;
153 long seek;
156 struct rect {
157 int left;
158 int right;
159 int top;
160 int bottom;
163 struct load_slide_event_data {
164 int slide_index;
165 int cache_index;
169 struct pfraw_header {
170 int32_t width; /* bmap width in pixels */
171 int32_t height; /* bmap height in pixels */
174 const struct picture logos[]={
175 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
178 struct config_data {
179 long avg_album_width;
180 int spacing_between_slides;
181 int extra_spacing_for_center_slide;
182 int show_slides;
183 int zoom;
186 /** below we allocate the memory we want to use **/
188 static fb_data *buffer; /* for now it always points to the lcd framebuffer */
189 static PFreal rays[BUFFER_WIDTH];
190 static struct slide_data center_slide;
191 static struct slide_data left_slides[MAX_SLIDES_COUNT];
192 static struct slide_data right_slides[MAX_SLIDES_COUNT];
193 static int slide_frame;
194 static int step;
195 static int target;
196 static int fade;
197 static int center_index; /* index of the slide that is in the center */
198 static int itilt;
199 static PFreal offsetX;
200 static PFreal offsetY;
201 static bool show_fps; /* show fps in the main screen */
202 static int number_of_slides;
204 static struct slide_cache cache[SLIDE_CACHE_SIZE];
205 static int slide_cache_in_use;
207 /* use long for aligning */
208 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
209 static int slide_cache_stack[SLIDE_CACHE_SIZE]; /* queue (as array) for scheduling load_surface */
210 static int slide_cache_stack_index;
211 struct mutex slide_cache_stack_lock;
213 static int empty_slide_hid;
215 struct thread_entry *thread_id;
216 struct event_queue thread_q;
218 static char tmp_path_name[MAX_PATH];
220 static long uniqbuf[UNIQBUF_SIZE];
221 static struct tagcache_search tcs;
223 static struct album_data album[MAX_ALBUMS];
224 static char album_names[MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH];
225 static int album_count;
227 static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
228 static struct track_data tracks[MAX_TRACKS];
229 static int track_count;
230 static int track_index;
231 static int selected_track;
232 static int selected_track_pulse;
234 static fb_data *input_bmp_buffer;
235 static fb_data *output_bmp_buffer;
236 static int input_hid;
237 static int output_hid;
238 static struct config_data config;
240 static int old_drawmode;
242 static bool thread_is_running;
244 static int cover_animation_keyframe;
245 static int extra_fade;
247 static int albumtxt_x = 0;
248 static int albumtxt_dir = -1;
249 static int prev_center_index = -1;
251 static int start_index_track_list = 0;
252 static int track_list_visible_entries = 0;
253 static int track_scroll_index = 0;
254 static int track_scroll_dir = 1;
257 Proposals for transitions:
259 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
260 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
262 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
264 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
266 TODO:
267 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
268 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
270 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
272 enum pf_states {
273 pf_idle = 0,
274 pf_scrolling,
275 pf_cover_in,
276 pf_show_tracks,
277 pf_cover_out
280 static int pf_state;
282 /** code */
284 bool create_bmp(struct bitmap* input_bmp, char *target_path, bool resize);
285 int load_surface(int);
287 static inline PFreal fmul(PFreal a, PFreal b)
289 return (a*b) >> PFREAL_SHIFT;
292 /* There are some precision issues when not using (long long) which in turn
293 takes very long to compute... I guess the best solution would be to optimize
294 the computations so it only requires a single long */
295 static inline PFreal fdiv(PFreal num, PFreal den)
297 long long p = (long long) (num) << (PFREAL_SHIFT * 2);
298 long long q = p / (long long) den;
299 long long r = q >> PFREAL_SHIFT;
301 return r;
304 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
305 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
306 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
309 #if 0
310 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
311 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
313 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
314 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
316 static inline PFreal fmul(PFreal a, PFreal b)
318 return (a*b) >> PFREAL_SHIFT;
321 static inline PFreal fdiv(PFreal n, PFreal m)
323 return (n<<(PFREAL_SHIFT))/m;
325 #endif
327 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
328 static const PFreal sin_tab[] = {
329 3, 103, 202, 300, 394, 485, 571, 652,
330 726, 793, 853, 904, 947, 980, 1004, 1019,
331 1023, 1018, 1003, 978, 944, 901, 849, 789,
332 721, 647, 566, 479, 388, 294, 196, 97,
333 -4, -104, -203, -301, -395, -486, -572, -653,
334 -727, -794, -854, -905, -948, -981, -1005, -1020,
335 -1024, -1019, -1004, -979, -945, -902, -850, -790,
336 -722, -648, -567, -480, -389, -295, -197, -98,
340 static inline PFreal fsin(int iangle)
342 while(iangle < 0)
343 iangle += IANGLE_MAX;
344 iangle &= IANGLE_MASK;
346 int i = (iangle >> 4);
347 PFreal p = sin_tab[i];
348 PFreal q = sin_tab[(i+1)];
349 PFreal g = (q - p);
350 return p + g * (iangle-i*16)/16;
353 static inline PFreal fcos(int iangle)
355 return fsin(iangle + (IANGLE_MAX >> 2));
359 Create an index of all albums from the database.
360 Also store the album names so we can access them later.
362 int create_album_index(void)
364 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
365 album_count = 0;
366 rb->tagcache_search(&tcs, tag_album);
367 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
368 int l, old_l = 0;
369 album[0].name_idx = 0;
370 while (rb->tagcache_get_next(&tcs) && album_count < MAX_ALBUMS)
372 l = rb->strlen(tcs.result) + 1;
373 if ( album_count > 0 )
374 album[album_count].name_idx = album[album_count-1].name_idx + old_l;
376 if ( (album[album_count].name_idx + l) > MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH )
377 /* not enough memory */
378 return ERROR_BUFFER_FULL;
380 rb->strcpy(album_names + album[album_count].name_idx, tcs.result);
381 album[album_count].seek = tcs.result_seek;
382 old_l = l;
383 album_count++;
385 rb->tagcache_search_finish(&tcs);
387 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
391 Return a pointer to the album name of the given slide_index
393 char* get_album_name(const int slide_index)
395 return album_names + album[slide_index].name_idx;
399 Return a pointer to the track name of the active album
400 create_track_index has to be called first.
402 char* get_track_name(const int track_index)
404 if ( track_index < track_count )
405 return track_names + tracks[track_index].name_idx;
406 return 0;
410 Create the track index of the given slide_index.
412 int create_track_index(const int slide_index)
414 if ( slide_index == track_index ) {
415 return -1;
418 if (!rb->tagcache_search(&tcs, tag_title))
419 return -1;
421 int ret = 0;
423 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
424 track_count=0;
425 int l, old_l = 0;
426 tracks[0].name_idx = 0;
428 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
430 l = rb->strlen(tcs.result) + 1;
431 if ( track_count > 0 )
432 tracks[track_count].name_idx = tracks[track_count-1].name_idx + old_l;
434 if ( (tracks[track_count].name_idx + l) > MAX_TRACKS * AVG_TRACK_NAME_LENGTH )
436 /* not enough memory */
437 ret = ERROR_BUFFER_FULL;
438 break;
440 rb->strcpy(track_names + tracks[track_count].name_idx, tcs.result);
441 tracks[track_count].seek = tcs.result_seek;
442 old_l = l;
443 track_count++;
446 rb->tagcache_search_finish(&tcs);
447 track_index = slide_index;
449 if (ret != 0)
450 return ret;
451 else
452 return (track_count > 0) ? 0 : -1;
457 Determine filename of the album art for the given slide_index and
458 store the result in buf.
459 The algorithm looks for the first track of the given album uses
460 find_albumart to find the filename.
462 bool get_albumart_for_index_from_db(const int slide_index, char *buf, int buflen)
464 if ( slide_index == -1 )
466 rb->strncpy( buf, EMPTY_SLIDE, buflen );
469 if (!rb->tagcache_search(&tcs, tag_filename))
470 return false;
472 bool result;
473 /* find the first track of the album */
474 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
476 if ( rb->tagcache_get_next(&tcs) ) {
477 struct mp3entry id3;
478 char size[9];
479 rb->snprintf(size, sizeof(size), ".%dx%d", PREFERRED_IMG_WIDTH,
480 PREFERRED_IMG_HEIGHT);
481 rb->strncpy( (char*)&id3.path, tcs.result, MAX_PATH );
482 id3.album = get_album_name(slide_index);
483 if ( rb->search_albumart_files(&id3, size, buf, buflen) )
484 result = true;
485 else if ( rb->search_albumart_files(&id3, "", buf, buflen) )
486 result = true;
487 else
488 result = false;
490 else {
491 /* did not find a matching track */
492 result = false;
494 rb->tagcache_search_finish(&tcs);
495 return result;
499 Draw the PictureFlow logo
501 void draw_splashscreen(void)
503 struct screen* display = rb->screens[0];
505 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
506 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
507 rb->lcd_clear_display();
509 const struct picture* logo = &(logos[display->screen_type]);
510 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
512 rb->lcd_update();
517 Draw a simple progress bar
519 void draw_progressbar(int step)
521 int txt_w, txt_h;
522 const int bar_height = 22;
523 const int w = LCD_WIDTH - 20;
524 const int x = 10;
526 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
528 int y = (LCD_HEIGHT - txt_h)/2;
530 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
531 y += (txt_h + 5);
533 rb->lcd_set_foreground(LCD_RGBPACK(100,100,100));
534 rb->lcd_drawrect(x, y, w+2, bar_height);
535 rb->lcd_set_foreground(LCD_RGBPACK(165, 231, 82));
537 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
538 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
539 rb->lcd_update();
540 rb->yield();
544 Allocate temporary buffers
546 bool allocate_buffers(void)
548 int input_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data );
549 int output_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data ) * 2;
551 input_hid = rb->bufalloc(NULL, input_size, TYPE_BITMAP);
553 if (input_hid < 0)
554 return false;
556 if (rb->bufgetdata(input_hid, 0, (void *)&input_bmp_buffer) < input_size) {
557 rb->bufclose(input_hid);
558 return false;
561 output_hid = rb->bufalloc(NULL, output_size, TYPE_BITMAP);
563 if (output_hid < 0) {
564 rb->bufclose(input_hid);
565 return false;
568 if (rb->bufgetdata(output_hid, 0, (void *)&output_bmp_buffer) < output_size) {
569 rb->bufclose(output_hid);
570 return false;
572 return true;
577 Free the temporary buffers
579 bool free_buffers(void)
581 rb->bufclose(input_hid);
582 rb->bufclose(output_hid);
583 return true;
587 Precomupte the album art images and store them in CACHE_PREFIX.
589 bool create_albumart_cache(bool force)
591 number_of_slides = album_count;
592 int fh,ret;
594 if ( ! force && rb->file_exists( CACHE_PREFIX "/ready" ) ) return true;
596 int i, slides = 0;
597 struct bitmap input_bmp;
599 config.avg_album_width = 0;
600 for (i=0; i < album_count; i++)
602 draw_progressbar(i);
603 if (!get_albumart_for_index_from_db(i, tmp_path_name, MAX_PATH))
604 continue;
606 input_bmp.data = (char *)input_bmp_buffer;
607 ret = rb->read_bmp_file(tmp_path_name, &input_bmp,
608 sizeof(fb_data)*MAX_IMG_WIDTH*MAX_IMG_HEIGHT,
609 FORMAT_NATIVE);
610 if (ret <= 0) {
611 rb->splash(HZ, "Could not read bmp");
612 continue; /* skip missing/broken files */
616 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw", i);
617 if (!create_bmp(&input_bmp, tmp_path_name, false)) {
618 rb->splash(HZ, "Could not write bmp");
620 config.avg_album_width += input_bmp.width;
621 slides++;
622 if ( rb->button_get(false) == PICTUREFLOW_MENU ) return false;
624 if ( slides == 0 ) {
625 rb->splash(2*HZ, "No albums found");
626 return false;
628 config.avg_album_width /= slides;
629 if ( config.avg_album_width == 0 ) {
630 rb->splash(HZ, "album size is 0");
631 return false;
633 fh = rb->creat( CACHE_PREFIX "/ready" );
634 rb->close(fh);
635 return true;
639 Return the index on the stack of slide_index.
640 Return -1 if slide_index is not on the stack.
642 static inline int slide_stack_get_index(const int slide_index)
644 int i = slide_cache_stack_index + 1;
645 while (i--) {
646 if ( slide_cache_stack[i] == slide_index ) return i;
648 return -1;
652 Push the slide_index on the stack so the image will be loaded.
653 The algorithm tries to keep the center_index on top and the
654 slide_index as high as possible (so second if center_index is
655 on the stack).
657 void slide_stack_push(const int slide_index)
659 rb->mutex_lock(&slide_cache_stack_lock);
661 if ( slide_cache_stack_index == -1 ) {
662 /* empty stack, no checks at all */
663 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
664 rb->mutex_unlock(&slide_cache_stack_lock);
665 return;
668 int i = slide_stack_get_index( slide_index );
670 if ( i == slide_cache_stack_index ) {
671 /* slide_index is on top, so we do not change anything */
672 rb->mutex_unlock(&slide_cache_stack_lock);
673 return;
676 if ( i >= 0 ) {
677 /* slide_index is already on the stack, but not on top */
678 int tmp = slide_cache_stack[ slide_cache_stack_index ];
679 if ( tmp == center_index ) {
680 /* the center_index is on top of the stack so do not touch that */
681 if ( slide_cache_stack_index > 0 ) {
682 /* but maybe it is possible to swap the given slide_index to the second place */
683 tmp = slide_cache_stack[ slide_cache_stack_index -1 ];
684 slide_cache_stack[ slide_cache_stack_index - 1 ] = slide_cache_stack[ i ];
685 slide_cache_stack[ i ] = tmp;
688 else {
689 /* if the center_index is not on top (i.e. already loaded) bring the slide_index to the top */
690 slide_cache_stack[ slide_cache_stack_index ] = slide_cache_stack[ i ];
691 slide_cache_stack[ i ] = tmp;
694 else {
695 /* slide_index is not on the stack */
696 if ( slide_cache_stack_index >= SLIDE_CACHE_SIZE-1 ) {
697 /* if we exceeded the stack size, clear the first half of the stack */
698 slide_cache_stack_index = SLIDE_CACHE_SIZE/2;
699 for (i = 0; i <= slide_cache_stack_index ; i++)
700 slide_cache_stack[ i ] = slide_cache_stack[ i + slide_cache_stack_index ];
702 if ( slide_cache_stack[ slide_cache_stack_index ] == center_index ) {
703 /* if the center_index is on top leave it there */
704 slide_cache_stack[ slide_cache_stack_index ] = slide_index;
705 slide_cache_stack[ ++slide_cache_stack_index ] = center_index;
707 else {
708 /* usual stack case: push the slide_index on top */
709 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
712 rb->mutex_unlock(&slide_cache_stack_lock);
717 Pop the topmost item from the stack and decrease the stack size
719 static inline int slide_stack_pop(void)
721 rb->mutex_lock(&slide_cache_stack_lock);
722 int result;
723 if ( slide_cache_stack_index >= 0 )
724 result = slide_cache_stack[ slide_cache_stack_index-- ];
725 else
726 result = -1;
727 rb->mutex_unlock(&slide_cache_stack_lock);
728 return result;
733 Load the slide into the cache.
734 Thus we have to queue the loading request in our thread while discarding the
735 oldest slide.
737 static inline void request_surface(const int slide_index)
739 slide_stack_push(slide_index);
740 rb->queue_post(&thread_q, EV_WAKEUP, 0);
745 Thread used for loading and preparing bitmaps in the background
747 void thread(void)
749 long sleep_time = 5 * HZ;
750 struct queue_event ev;
751 while (1) {
752 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
753 switch (ev.id) {
754 case EV_EXIT:
755 return;
756 case EV_WAKEUP:
757 /* we just woke up */
758 break;
760 int slide_index;
761 while ( (slide_index = slide_stack_pop()) != -1 ) {
762 load_surface( slide_index );
763 rb->queue_wait_w_tmo(&thread_q, &ev, HZ/10);
764 switch (ev.id) {
765 case EV_EXIT:
766 return;
774 End the thread by posting the EV_EXIT event
776 void end_pf_thread(void)
778 if ( thread_is_running ) {
779 rb->queue_post(&thread_q, EV_EXIT, 0);
780 rb->thread_wait(thread_id);
781 /* remove the thread's queue from the broadcast list */
782 rb->queue_delete(&thread_q);
783 thread_is_running = false;
790 Create the thread an setup the event queue
792 bool create_pf_thread(void)
794 rb->queue_init(&thread_q, true); /* put the thread's queue in the bcast list */
795 if ((thread_id = rb->create_thread(
796 thread,
797 thread_stack,
798 sizeof(thread_stack),
800 "Picture load thread"
801 IF_PRIO(, PRIORITY_BACKGROUND)
802 IF_COP(, CPU)
804 ) == NULL) {
805 return false;
807 thread_is_running = true;
808 return true;
812 Safe the given bitmap as filename in the pfraw format
814 bool save_pfraw(char* filename, struct bitmap *bm)
816 struct pfraw_header bmph;
817 bmph.width = bm->width;
818 bmph.height = bm->height;
819 int fh = rb->creat( filename );
820 if( fh < 0 ) return false;
821 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
822 int y;
823 for( y = 0; y < bm->height; y++ )
825 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
826 rb->write( fh, d, sizeof( fb_data ) * bm->width );
828 rb->close( fh );
829 return true;
834 Read the pfraw image given as filename and return the hid of the buffer
836 int read_pfraw(char* filename)
838 struct pfraw_header bmph;
839 int fh = rb->open(filename, O_RDONLY);
840 rb->read(fh, &bmph, sizeof(struct pfraw_header));
841 if( fh < 0 ) {
842 return empty_slide_hid;
845 int size = sizeof(struct bitmap) + sizeof( fb_data ) * bmph.width * bmph.height;
847 int hid = rb->bufalloc(NULL, size, TYPE_BITMAP);
848 if (hid < 0) {
849 rb->close( fh );
850 return -1;
853 struct bitmap *bm;
854 if (rb->bufgetdata(hid, 0, (void *)&bm) < size) {
855 rb->close( fh );
856 return -1;
859 bm->width = bmph.width;
860 bm->height = bmph.height;
861 bm->format = FORMAT_NATIVE;
862 bm->data = ((unsigned char *)bm + sizeof(struct bitmap));
864 int y;
865 for( y = 0; y < bm->height; y++ )
867 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
868 rb->read( fh, d , sizeof( fb_data ) * bm->width );
870 rb->close( fh );
871 return hid;
876 Create the slide with its reflection for the given slide_index and filename
877 and store it as pfraw in CACHE_PREFIX/[slide_index].pfraw
879 bool create_bmp(struct bitmap *input_bmp, char *target_path, bool resize)
881 struct bitmap output_bmp;
883 output_bmp.format = input_bmp->format;
884 output_bmp.data = (char *)output_bmp_buffer;
886 if ( resize ) {
887 /* resize image */
888 output_bmp.width = config.avg_album_width;
889 output_bmp.height = config.avg_album_width;
890 simple_resize_bitmap(input_bmp, &output_bmp);
892 /* Resized bitmap is now in the output buffer,
893 copy it back to the input buffer */
894 rb->memcpy(input_bmp_buffer, output_bmp_buffer,
895 config.avg_album_width * config.avg_album_width * sizeof(fb_data));
896 input_bmp->data = (char *)input_bmp_buffer;
897 input_bmp->width = output_bmp.width;
898 input_bmp->height = output_bmp.height;
901 output_bmp.width = input_bmp->width * 2;
902 output_bmp.height = input_bmp->height;
904 fb_data *src = (fb_data *)input_bmp->data;
905 fb_data *dst = (fb_data *)output_bmp.data;
907 /* transpose the image, this is to speed-up the rendering
908 because we process one column at a time
909 (and much better and faster to work row-wise, i.e in one scanline) */
910 int hofs = input_bmp->width / 3;
911 rb->memset(dst, 0, sizeof(fb_data) * output_bmp.width * output_bmp.height);
912 int x, y;
913 for (x = 0; x < input_bmp->width; x++)
914 for (y = 0; y < input_bmp->height; y++)
915 dst[output_bmp.width * x + (hofs + y)] =
916 src[y * input_bmp->width + x];
918 /* create the reflection */
919 int ht = input_bmp->height - hofs;
920 int hte = ht;
921 for (x = 0; x < input_bmp->width; x++) {
922 for (y = 0; y < ht; y++) {
923 fb_data color = src[x + input_bmp->width * (input_bmp->height - y - 1)];
924 int r = RGB_UNPACK_RED(color) * (hte - y) / hte * 3 / 5;
925 int g = RGB_UNPACK_GREEN(color) * (hte - y) / hte * 3 / 5;
926 int b = RGB_UNPACK_BLUE(color) * (hte - y) / hte * 3 / 5;
927 dst[output_bmp.height + hofs + y + output_bmp.width * x] =
928 LCD_RGBPACK(r, g, b);
931 return save_pfraw(target_path, &output_bmp);
936 Load the surface for the given slide_index into the cache at cache_index.
938 static inline bool load_and_prepare_surface(const int slide_index,
939 const int cache_index)
941 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
942 slide_index);
944 int hid = read_pfraw(tmp_path_name);
945 if (hid < 0)
946 return false;
948 cache[cache_index].hid = hid;
950 if ( cache_index < SLIDE_CACHE_SIZE ) {
951 cache[cache_index].index = slide_index;
952 cache[cache_index].touched = *rb->current_tick;
955 return true;
960 Load the surface from a bmp and overwrite the oldest slide in the cache
961 if necessary.
963 int load_surface(const int slide_index)
965 long oldest_tick = *rb->current_tick;
966 int oldest_slide = 0;
967 int i;
968 if ( slide_cache_in_use < SLIDE_CACHE_SIZE ) { /* initial fill */
969 oldest_slide = slide_cache_in_use;
970 load_and_prepare_surface(slide_index, slide_cache_in_use++);
972 else {
973 for (i = 0; i < SLIDE_CACHE_SIZE; i++) { /* look for oldest slide */
974 if (cache[i].touched < oldest_tick) {
975 oldest_slide = i;
976 oldest_tick = cache[i].touched;
979 if (cache[oldest_slide].hid != empty_slide_hid) {
980 rb->bufclose(cache[oldest_slide].hid);
981 cache[oldest_slide].hid = -1;
983 load_and_prepare_surface(slide_index, oldest_slide);
985 return oldest_slide;
990 Get a slide from the buffer
992 static inline struct bitmap *get_slide(const int hid)
994 if (hid < 0)
995 return NULL;
997 struct bitmap *bmp;
999 ssize_t ret = rb->bufgetdata(hid, 0, (void *)&bmp);
1000 if (ret < 0)
1001 return NULL;
1003 return bmp;
1008 Return the requested surface
1010 static inline struct bitmap *surface(const int slide_index)
1012 if (slide_index < 0)
1013 return 0;
1014 if (slide_index >= number_of_slides)
1015 return 0;
1017 int i;
1018 for (i = 0; i < slide_cache_in_use; i++) { /* maybe do the inverse mapping => implies dynamic allocation? */
1019 if ( cache[i].index == slide_index ) {
1020 /* We have already loaded our slide, so touch it and return it. */
1021 cache[i].touched = *rb->current_tick;
1022 return get_slide(cache[i].hid);
1025 request_surface(slide_index);
1026 return get_slide(empty_slide_hid);
1030 adjust slides so that they are in "steady state" position
1032 void reset_slides(void)
1034 center_slide.angle = 0;
1035 center_slide.cx = 0;
1036 center_slide.cy = 0;
1037 center_slide.distance = 0;
1038 center_slide.slide_index = center_index;
1040 int i;
1041 for (i = 0; i < config.show_slides; i++) {
1042 struct slide_data *si = &left_slides[i];
1043 si->angle = itilt;
1044 si->cx = -(offsetX + config.spacing_between_slides * i * PFREAL_ONE);
1045 si->cy = offsetY;
1046 si->slide_index = center_index - 1 - i;
1047 si->distance = 0;
1050 for (i = 0; i < config.show_slides; i++) {
1051 struct slide_data *si = &right_slides[i];
1052 si->angle = -itilt;
1053 si->cx = offsetX + config.spacing_between_slides * i * PFREAL_ONE;
1054 si->cy = offsetY;
1055 si->slide_index = center_index + 1 + i;
1056 si->distance = 0;
1062 Updates look-up table and other stuff necessary for the rendering.
1063 Call this when the viewport size or slide dimension is changed.
1065 void recalc_table(void)
1067 int w = (BUFFER_WIDTH + 1) / 2;
1068 int h = (BUFFER_HEIGHT + 1) / 2;
1069 int i;
1070 for (i = 0; i < w; i++) {
1071 PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2 * h);
1072 rays[w - i - 1] = -gg;
1073 rays[w + i] = gg;
1076 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1078 offsetX = config.avg_album_width / 2 * (PFREAL_ONE - fcos(itilt));
1079 offsetY = config.avg_album_width / 2 * fsin(itilt);
1080 offsetX += config.avg_album_width * PFREAL_ONE;
1081 offsetY += config.avg_album_width * PFREAL_ONE / 4;
1082 offsetX += config.extra_spacing_for_center_slide << PFREAL_SHIFT;
1087 Fade the given color by spreading the fb_data (ushort)
1088 to an uint, multiply and compress the result back to a ushort.
1090 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1091 static inline fb_data fade_color(fb_data c, unsigned int a)
1093 c = swap16(c);
1094 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1095 return swap16( (fb_data) (p | ( p >> 16 )) );
1097 #else
1098 static inline fb_data fade_color(fb_data c, unsigned int a)
1100 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1101 return (p | ( p >> 16 ));
1103 #endif
1109 Render a single slide
1111 void render_slide(struct slide_data *slide, struct rect *result_rect,
1112 const int alpha, int col1, int col2)
1114 rb->memset(result_rect, 0, sizeof(struct rect));
1115 struct bitmap *bmp = surface(slide->slide_index);
1116 if (!bmp) {
1117 return;
1119 fb_data *src = (fb_data *)bmp->data;
1121 const int sw = bmp->height;
1122 const int sh = bmp->width;
1124 const int h = LCD_HEIGHT;
1125 const int w = LCD_WIDTH;
1127 if (col1 > col2) {
1128 int c = col2;
1129 col2 = col1;
1130 col1 = c;
1133 col1 = (col1 >= 0) ? col1 : 0;
1134 col2 = (col2 >= 0) ? col2 : w - 1;
1135 col1 = fmin(col1, w - 1);
1136 col2 = fmin(col2, w - 1);
1138 int distance = (h + slide->distance) * 100 / config.zoom;
1139 if (distance < 100 ) distance = 100; /* clamp distances */
1140 PFreal sdx = fcos(slide->angle);
1141 PFreal sdy = fsin(slide->angle);
1142 PFreal xs = slide->cx - bmp->width * sdx / 4;
1143 PFreal ys = slide->cy - bmp->width * sdy / 4;
1144 PFreal dist = distance * PFREAL_ONE;
1146 const int alpha4 = alpha >> 3;
1148 int xi = fmax((PFreal) 0,
1149 ((w * PFREAL_ONE / 2) +
1150 fdiv(xs * h, dist + ys)) >> PFREAL_SHIFT);
1151 if (xi >= w) {
1152 return;
1155 bool flag = false;
1156 result_rect->left = xi;
1157 int x;
1158 for (x = fmax(xi, col1); x <= col2; x++) {
1159 PFreal hity = 0;
1160 PFreal fk = rays[x];
1161 if (sdy) {
1162 fk = fk - fdiv(sdx, sdy);
1163 hity = -fdiv(( rays[x] * distance
1164 - slide->cx
1165 + slide->cy * sdx / sdy), fk);
1168 dist = distance * PFREAL_ONE + hity;
1169 if (dist < 0)
1170 continue;
1172 PFreal hitx = fmul(dist, rays[x]);
1174 PFreal hitdist = fdiv(hitx - slide->cx, sdx);
1176 const int column = (sw >> 1) + (hitdist >> PFREAL_SHIFT);
1177 if (column >= sw)
1178 break;
1180 if (column < 0)
1181 continue;
1183 result_rect->right = x;
1184 if (!flag)
1185 result_rect->left = x;
1186 flag = true;
1188 int y1 = (h >> 1);
1189 int y2 = y1 + 1;
1190 fb_data *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
1191 fb_data *pixel2 = &buffer[y2 * BUFFER_WIDTH + x];
1192 const int pixelstep = pixel2 - pixel1;
1194 int center = (sh >> 1);
1195 int dy = dist / h;
1196 int p1 = center * PFREAL_ONE - (dy >> 2);
1197 int p2 = center * PFREAL_ONE + (dy >> 2);
1199 const fb_data *ptr = &src[column * bmp->width];
1201 if (alpha == 256)
1202 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1203 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
1204 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
1205 p1 -= dy;
1206 p2 += dy;
1207 y1--;
1208 y2++;
1209 pixel1 -= pixelstep;
1210 pixel2 += pixelstep;
1211 } else
1212 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1213 *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT], alpha4);
1214 *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT], alpha4);
1215 p1 -= dy;
1216 p2 += dy;
1217 y1--;
1218 y2++;
1219 pixel1 -= pixelstep;
1220 pixel2 += pixelstep;
1223 /* let the music play... */
1224 rb->yield();
1226 result_rect->top = 0;
1227 result_rect->bottom = h - 1;
1228 return;
1233 Jump the the given slide_index
1235 static inline void set_current_slide(const int slide_index)
1237 step = 0;
1238 center_index = fbound(slide_index, 0, number_of_slides - 1);
1239 target = center_index;
1240 slide_frame = slide_index << 16;
1241 reset_slides();
1245 Start the animation for changing slides
1247 void start_animation(void)
1249 step = (target < center_slide.slide_index) ? -1 : 1;
1250 pf_state = pf_scrolling;
1254 Go to the previous slide
1256 void show_previous_slide(void)
1258 if (step == 0) {
1259 if (center_index > 0) {
1260 target = center_index - 1;
1261 start_animation();
1263 } else if ( step > 0 ) {
1264 target = center_index;
1265 start_animation();
1266 } else {
1267 target = fmax(0, center_index - 2);
1273 Go to the next slide
1275 void show_next_slide(void)
1277 if (step == 0) {
1278 if (center_index < number_of_slides - 1) {
1279 target = center_index + 1;
1280 start_animation();
1282 } else if ( step < 0 ) {
1283 target = center_index;
1284 start_animation();
1285 } else {
1286 target = fmin(center_index + 2, number_of_slides - 1);
1292 Return true if the rect has size 0
1294 static inline bool is_empty_rect(struct rect *r)
1296 return ((r->left == 0) && (r->right == 0) && (r->top == 0)
1297 && (r->bottom == 0));
1302 Render the slides. Updates only the offscreen buffer.
1304 void render_all_slides(void)
1306 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
1307 rb->lcd_clear_display(); /* TODO: Optimizes this by e.g. invalidating rects */
1309 int nleft = config.show_slides;
1310 int nright = config.show_slides;
1312 struct rect r;
1313 r.left = LCD_WIDTH; r.top = 0; r.bottom = 0; r.right = 0;
1314 render_slide(&center_slide, &r, 256, -1, -1);
1315 #ifdef DEBUG_DRAW
1316 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1317 #endif
1318 int c1 = r.left;
1319 int c2 = r.right;
1320 int index;
1321 if (step == 0) {
1322 /* no animation, boring plain rendering */
1323 for (index = 0; index < nleft - 1; index++) {
1324 int alpha = (index < nleft - 2) ? 256 : 128;
1325 alpha -= extra_fade;
1326 if (alpha < 0 ) alpha = 0;
1327 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1328 if (!is_empty_rect(&r)) {
1329 #ifdef DEBUG_DRAW
1330 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1331 #endif
1332 c1 = r.left;
1335 for (index = 0; index < nright - 1; index++) {
1336 int alpha = (index < nright - 2) ? 256 : 128;
1337 alpha -= extra_fade;
1338 if (alpha < 0 ) alpha = 0;
1339 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1340 BUFFER_WIDTH);
1341 if (!is_empty_rect(&r)) {
1342 #ifdef DEBUG_DRAW
1343 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1344 #endif
1345 c2 = r.right;
1348 } else {
1349 if ( step < 0 ) c1 = BUFFER_WIDTH;
1350 /* the first and last slide must fade in/fade out */
1351 for (index = 0; index < nleft; index++) {
1352 int alpha = 256;
1353 if (index == nleft - 1)
1354 alpha = (step > 0) ? 0 : 128 - fade / 2;
1355 if (index == nleft - 2)
1356 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1357 if (index == nleft - 3)
1358 alpha = (step > 0) ? 256 - fade / 2 : 256;
1359 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1361 if (!is_empty_rect(&r)) {
1362 #ifdef DEBUG_DRAW
1363 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1364 #endif
1365 c1 = r.left;
1368 if ( step > 0 ) c2 = 0;
1369 for (index = 0; index < nright; index++) {
1370 int alpha = (index < nright - 2) ? 256 : 128;
1371 if (index == nright - 1)
1372 alpha = (step > 0) ? fade / 2 : 0;
1373 if (index == nright - 2)
1374 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1375 if (index == nright - 3)
1376 alpha = (step > 0) ? 256 : 128 + fade / 2;
1377 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1378 BUFFER_WIDTH);
1379 if (!is_empty_rect(&r)) {
1380 #ifdef DEBUG_DRAW
1381 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1382 #endif
1383 c2 = r.right;
1391 Updates the animation effect. Call this periodically from a timer.
1393 void update_scroll_animation(void)
1395 if (step == 0)
1396 return;
1398 int speed = 16384;
1399 int i;
1401 /* deaccelerate when approaching the target */
1402 if (true) {
1403 const int max = 2 * 65536;
1405 int fi = slide_frame;
1406 fi -= (target << 16);
1407 if (fi < 0)
1408 fi = -fi;
1409 fi = fmin(fi, max);
1411 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1412 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1415 slide_frame += speed * step;
1417 int index = slide_frame >> 16;
1418 int pos = slide_frame & 0xffff;
1419 int neg = 65536 - pos;
1420 int tick = (step < 0) ? neg : pos;
1421 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1423 /* the leftmost and rightmost slide must fade away */
1424 fade = pos / 256;
1426 if (step < 0)
1427 index++;
1428 if (center_index != index) {
1429 center_index = index;
1430 slide_frame = index << 16;
1431 center_slide.slide_index = center_index;
1432 for (i = 0; i < config.show_slides; i++)
1433 left_slides[i].slide_index = center_index - 1 - i;
1434 for (i = 0; i < config.show_slides; i++)
1435 right_slides[i].slide_index = center_index + 1 + i;
1438 center_slide.angle = (step * tick * itilt) >> 16;
1439 center_slide.cx = -step * fmul(offsetX, ftick);
1440 center_slide.cy = fmul(offsetY, ftick);
1442 if (center_index == target) {
1443 reset_slides();
1444 pf_state = pf_idle;
1445 step = 0;
1446 fade = 256;
1447 return;
1450 for (i = 0; i < config.show_slides; i++) {
1451 struct slide_data *si = &left_slides[i];
1452 si->angle = itilt;
1453 si->cx =
1454 -(offsetX + config.spacing_between_slides * i * PFREAL_ONE + step
1455 * config.spacing_between_slides * ftick);
1456 si->cy = offsetY;
1459 for (i = 0; i < config.show_slides; i++) {
1460 struct slide_data *si = &right_slides[i];
1461 si->angle = -itilt;
1462 si->cx =
1463 offsetX + config.spacing_between_slides * i * PFREAL_ONE - step
1464 * config.spacing_between_slides * ftick;
1465 si->cy = offsetY;
1468 if (step > 0) {
1469 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1470 right_slides[0].angle = -(neg * itilt) >> 16;
1471 right_slides[0].cx = fmul(offsetX, ftick);
1472 right_slides[0].cy = fmul(offsetY, ftick);
1473 } else {
1474 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1475 left_slides[0].angle = (pos * itilt) >> 16;
1476 left_slides[0].cx = -fmul(offsetX, ftick);
1477 left_slides[0].cy = fmul(offsetY, ftick);
1480 /* must change direction ? */
1481 if (target < index)
1482 if (step > 0)
1483 step = -1;
1484 if (target > index)
1485 if (step < 0)
1486 step = 1;
1491 Cleanup the plugin
1493 void cleanup(void *parameter)
1495 (void) parameter;
1496 /* Turn on backlight timeout (revert to settings) */
1497 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1499 int i;
1500 for (i = 0; i < slide_cache_in_use; i++) {
1501 rb->bufclose(cache[i].hid);
1503 if ( empty_slide_hid != - 1)
1504 rb->bufclose(empty_slide_hid);
1505 rb->lcd_set_drawmode(old_drawmode);
1509 Create the "?" slide, that is shown while loading
1510 or when no cover was found.
1512 int create_empty_slide(bool force)
1514 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1515 struct bitmap input_bmp;
1516 input_bmp.width = BMPWIDTH_pictureflow_emptyslide;
1517 input_bmp.height = BMPHEIGHT_pictureflow_emptyslide;
1518 input_bmp.format = FORMAT_NATIVE;
1519 input_bmp.data = (char*) &pictureflow_emptyslide;
1520 if ( ! create_bmp(&input_bmp, EMPTY_SLIDE, true) ) return false;
1523 empty_slide_hid = read_pfraw( EMPTY_SLIDE );
1524 if (empty_slide_hid == -1 ) return false;
1526 return true;
1531 Shows the settings menu
1533 int settings_menu(void) {
1534 int selection = 0;
1536 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1537 "Spacing", "Center margin", "Number of slides", "Zoom",
1538 "Rebuild cache");
1540 do {
1541 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1542 switch(selection) {
1543 case 0:
1544 rb->set_bool("Show FPS", &show_fps);
1545 break;
1547 case 1:
1548 rb->set_int("Spacing between slides", "", 1,
1549 &(config.spacing_between_slides),
1550 NULL, 1, 0, 100, NULL );
1551 recalc_table();
1552 reset_slides();
1553 break;
1555 case 2:
1556 rb->set_int("Center margin", "", 1,
1557 &(config.extra_spacing_for_center_slide),
1558 NULL, 1, -50, 50, NULL );
1559 recalc_table();
1560 reset_slides();
1561 break;
1563 case 3:
1564 rb->set_int("Number of slides", "", 1, &(config.show_slides),
1565 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1566 recalc_table();
1567 reset_slides();
1568 break;
1570 case 4:
1571 rb->set_int("Number of slides", "", 1, &(config.zoom),
1572 NULL, 1, 10, 300, NULL );
1573 recalc_table();
1574 reset_slides();
1575 break;
1577 case 5:
1578 rb->remove(CACHE_PREFIX "/ready");
1579 rb->remove(EMPTY_SLIDE);
1580 rb->splash(HZ, "Cache will be rebuilt on next restart");
1581 break;
1583 case MENU_ATTACHED_USB:
1584 return PLUGIN_USB_CONNECTED;
1586 } while ( selection >= 0 );
1587 return 0;
1591 Show the main menu
1593 int main_menu(void)
1595 int selection = 0;
1596 int result;
1598 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1600 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
1601 "Settings", "Return", "Quit");
1603 while (1) {
1604 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
1605 case 0:
1606 result = settings_menu();
1607 if ( result != 0 ) return result;
1608 break;
1610 case 1:
1611 return 0;
1613 case 2:
1614 return -1;
1616 case MENU_ATTACHED_USB:
1617 return PLUGIN_USB_CONNECTED;
1619 default:
1620 return 0;
1626 Fill the config struct with some defaults
1628 void set_default_config(void)
1630 config.spacing_between_slides = 40;
1631 config.extra_spacing_for_center_slide = 0;
1632 config.show_slides = 3;
1633 config.avg_album_width = 0;
1634 config.zoom = 100;
1638 Read the config file.
1639 For now, the size has to match.
1640 Later a version number might be appropiate.
1642 bool read_pfconfig(void)
1644 set_default_config();
1645 /* defaults */
1646 int fh = rb->open( CONFIG_FILE, O_RDONLY );
1647 if ( fh < 0 ) { /* no config yet */
1648 return true;
1650 int ret = rb->read(fh, &config, sizeof(struct config_data));
1651 rb->close(fh);
1652 if ( ret != sizeof(struct config_data) ) {
1653 set_default_config();
1654 rb->splash(2*HZ, "Config invalid. Using defaults");
1656 return true;
1660 Write the config file
1662 bool write_pfconfig(void)
1664 int fh = rb->creat( CONFIG_FILE );
1665 if( fh < 0 ) return false;
1666 rb->write( fh, &config, sizeof( struct config_data ) );
1667 rb->close( fh );
1668 return true;
1672 Animation step for zooming into the current cover
1674 void update_cover_in_animation(void)
1676 cover_animation_keyframe++;
1677 if( cover_animation_keyframe < 20 ) {
1678 center_slide.distance-=5;
1679 center_slide.angle+=1;
1680 extra_fade += 13;
1682 else if( cover_animation_keyframe < 35 ) {
1683 center_slide.angle+=16;
1685 else {
1686 cover_animation_keyframe = 0;
1687 selected_track = 0;
1688 pf_state = pf_show_tracks;
1693 Animation step for zooming out the current cover
1695 void update_cover_out_animation(void)
1697 cover_animation_keyframe++;
1698 if( cover_animation_keyframe <= 15 ) {
1699 center_slide.angle-=16;
1701 else if( cover_animation_keyframe < 35 ) {
1702 center_slide.distance+=5;
1703 center_slide.angle-=1;
1704 extra_fade -= 13;
1706 else {
1707 cover_animation_keyframe = 0;
1708 pf_state = pf_idle;
1713 Draw a blue gradient at y with height h
1715 static inline void draw_gradient(int y, int h)
1717 static int r, inc, c;
1718 inc = (100 << 8) / h;
1719 c = 0;
1720 selected_track_pulse = (selected_track_pulse+1) % 10;
1721 int c2 = selected_track_pulse - 5;
1722 for (r=0; r<h; r++) {
1723 rb->lcd_set_foreground(LCD_RGBPACK(c2+80-(c >> 9), c2+100-(c >> 9),
1724 c2+250-(c >> 8)));
1725 rb->lcd_hline(0, LCD_WIDTH, r+y);
1726 if ( r > h/2 )
1727 c-=inc;
1728 else
1729 c+=inc;
1735 Reset the track list after a album change
1737 void reset_track_list(void)
1739 int albumtxt_w, albumtxt_h;
1740 const char* albumtxt = get_album_name(center_index);
1741 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1742 const int height = LCD_HEIGHT-albumtxt_h-10;
1743 track_list_visible_entries = fmin( height/albumtxt_h , track_count );
1744 start_index_track_list = 0;
1745 track_scroll_index = 0;
1746 track_scroll_dir = 1;
1747 selected_track = 0;
1751 Display the list of tracks
1753 void show_track_list(void)
1755 rb->lcd_clear_display();
1756 if ( center_slide.slide_index != track_index ) {
1757 create_track_index(center_slide.slide_index);
1758 reset_track_list();
1760 static int titletxt_w, titletxt_h, titletxt_y, titletxt_x, i, color;
1761 titletxt_y = 0;
1762 int track_i;
1763 for (i=0; i < track_list_visible_entries; i++) {
1764 track_i = i+start_index_track_list;
1765 rb->lcd_getstringsize(get_track_name(track_i), &titletxt_w, &titletxt_h);
1766 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
1767 if ( track_i == selected_track ) {
1768 draw_gradient(titletxt_y, titletxt_h);
1769 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1770 if (titletxt_w > LCD_WIDTH ) {
1771 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
1772 track_scroll_dir = 1;
1773 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
1774 track_scroll_index += track_scroll_dir*2;
1775 titletxt_x = track_scroll_index;
1777 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1779 else {
1780 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
1781 rb->lcd_set_foreground(LCD_RGBPACK(color,color,color));
1782 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1784 titletxt_y += titletxt_h;
1788 void select_next_track(void)
1790 if ( selected_track < track_count - 1 ) {
1791 selected_track++;
1792 track_scroll_index = 0;
1793 track_scroll_dir = 1;
1794 if (selected_track==(track_list_visible_entries+start_index_track_list))
1795 start_index_track_list++;
1799 void select_prev_track(void)
1801 if (selected_track > 0 ) {
1802 if (selected_track==start_index_track_list) start_index_track_list--;
1803 track_scroll_index = 0;
1804 track_scroll_dir = 1;
1805 selected_track--;
1810 Draw the current album name
1812 void draw_album_text(void)
1814 int albumtxt_w, albumtxt_h;
1815 int albumtxt_y = 0;
1817 char *albumtxt;
1818 int c;
1819 /* Draw album text */
1820 if ( pf_state == pf_scrolling ) {
1821 c = ((slide_frame & 0xffff )/ 255);
1822 if (step < 0) c = 255-c;
1823 if (c > 128 ) { /* half way to next slide .. still not perfect! */
1824 albumtxt = get_album_name(center_index+step);
1825 c = (c-128)*2;
1827 else {
1828 albumtxt = get_album_name(center_index);
1829 c = (128-c)*2;
1832 else {
1833 c= 255;
1834 albumtxt = get_album_name(center_index);
1837 rb->lcd_set_foreground(LCD_RGBPACK(c,c,c));
1838 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1839 if (center_index != prev_center_index) {
1840 albumtxt_x = 0;
1841 albumtxt_dir = -1;
1842 prev_center_index = center_index;
1844 albumtxt_y = LCD_HEIGHT-albumtxt_h-10;
1846 if (albumtxt_w > LCD_WIDTH ) {
1847 rb->lcd_putsxy(albumtxt_x, albumtxt_y , albumtxt);
1848 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
1849 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
1850 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
1851 albumtxt_x += albumtxt_dir;
1854 else {
1855 rb->lcd_putsxy((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
1863 Main function that also contain the main plasma
1864 algorithm.
1866 int main(void)
1868 int ret;
1869 draw_splashscreen();
1871 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
1872 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
1873 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
1874 return PLUGIN_ERROR;
1878 if (!read_pfconfig()) {
1879 rb->splash(HZ, "Error in config. Please delete " CONFIG_FILE);
1880 return PLUGIN_ERROR;
1883 if (!allocate_buffers()) {
1884 rb->splash(HZ, "Could not allocate temporary buffers");
1885 return PLUGIN_ERROR;
1888 ret = create_album_index();
1889 if (ret == ERROR_BUFFER_FULL) {
1890 rb->splash(HZ, "Not enough memory for album names");
1891 return PLUGIN_ERROR;
1892 } else if (ret == ERROR_NO_ALBUMS) {
1893 rb->splash(HZ, "No albums found. Please enable database");
1894 return PLUGIN_ERROR;
1897 if (!create_albumart_cache(config.avg_album_width == 0)) {
1898 rb->splash(HZ, "Could not create album art cache");
1899 return PLUGIN_ERROR;
1902 if (!create_empty_slide(false)) {
1903 rb->splash(HZ, "Could not load the empty slide");
1904 return PLUGIN_ERROR;
1907 if (!free_buffers()) {
1908 rb->splash(HZ, "Could not free temporary buffers");
1909 return PLUGIN_ERROR;
1912 if (!create_pf_thread()) {
1913 rb->splash(HZ, "Cannot create thread!");
1914 return PLUGIN_ERROR;
1917 int i;
1919 /* initialize */
1920 int min_slide_cache = fmin(number_of_slides, SLIDE_CACHE_SIZE);
1921 for (i = 0; i < min_slide_cache; i++) {
1922 cache[i].hid = -1;
1923 cache[i].touched = 0;
1924 slide_cache_stack[i] = SLIDE_CACHE_SIZE-i-1;
1926 slide_cache_stack_index = min_slide_cache-1;
1927 slide_cache_in_use = 0;
1928 buffer = rb->lcd_framebuffer;
1930 pf_state = pf_idle;
1932 track_index = -1;
1933 extra_fade = 0;
1934 center_index = 0;
1935 slide_frame = 0;
1936 step = 0;
1937 target = 0;
1938 fade = 256;
1939 show_fps = false;
1941 recalc_table();
1942 reset_slides();
1944 char fpstxt[10];
1945 int button;
1947 int frames = 0;
1948 long last_update = *rb->current_tick;
1949 long current_update;
1950 long update_interval = 100;
1951 int fps = 0;
1953 bool instant_update;
1954 old_drawmode = rb->lcd_get_drawmode();
1955 rb->lcd_set_drawmode(DRMODE_FG);
1956 while (true) {
1957 current_update = *rb->current_tick;
1958 frames++;
1960 /* Initial rendering */
1961 instant_update = false;
1963 /* Handle states */
1964 switch ( pf_state ) {
1965 case pf_scrolling:
1966 update_scroll_animation();
1967 render_all_slides();
1968 instant_update = true;
1969 break;
1970 case pf_cover_in:
1971 update_cover_in_animation();
1972 render_all_slides();
1973 instant_update = true;
1974 break;
1975 case pf_cover_out:
1976 update_cover_out_animation();
1977 render_all_slides();
1978 instant_update = true;
1979 break;
1980 case pf_show_tracks:
1981 show_track_list();
1982 break;
1983 case pf_idle:
1984 render_all_slides();
1985 break;
1988 /* Calculate FPS */
1989 if (current_update - last_update > update_interval) {
1990 fps = frames * HZ / (current_update - last_update);
1991 last_update = current_update;
1992 frames = 0;
1995 /* Draw FPS */
1996 if (show_fps) {
1997 rb->lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
1998 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
1999 rb->lcd_putsxy(0, 0, fpstxt);
2002 draw_album_text();
2005 /* Copy offscreen buffer to LCD and give time to other threads */
2006 rb->lcd_update();
2007 rb->yield();
2009 /*/ Handle buttons */
2010 button = pluginlib_getaction(rb, instant_update ? 0 : HZ/16,
2011 plugin_contexts, NB_ACTION_CONTEXTS);
2013 switch (button) {
2014 case PICTUREFLOW_QUIT:
2015 return PLUGIN_OK;
2017 case PICTUREFLOW_MENU:
2018 if ( pf_state == pf_idle || pf_state == pf_scrolling ) {
2019 ret = main_menu();
2020 if ( ret == -1 ) return PLUGIN_OK;
2021 if ( ret != 0 ) return i;
2022 rb->lcd_set_drawmode(DRMODE_FG);
2024 else {
2025 pf_state = pf_cover_out;
2027 break;
2029 case PICTUREFLOW_NEXT_ALBUM:
2030 case PICTUREFLOW_NEXT_ALBUM_REPEAT:
2031 #ifdef SCROLLWHEEL
2032 if ( pf_state == pf_show_tracks )
2033 select_next_track();
2034 #endif
2035 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2036 show_next_slide();
2037 break;
2039 case PICTUREFLOW_PREV_ALBUM:
2040 case PICTUREFLOW_PREV_ALBUM_REPEAT:
2041 #ifdef SCROLLWHEEL
2042 if ( pf_state == pf_show_tracks )
2043 select_prev_track();
2044 #endif
2045 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2046 show_previous_slide();
2047 break;
2049 #ifndef SCROLLWHEEL
2050 case PICTUREFLOW_NEXT_TRACK:
2051 case PICTUREFLOW_NEXT_TRACK_REPEAT:
2052 if ( pf_state == pf_show_tracks )
2053 select_next_track();
2054 break;
2056 case PICTUREFLOW_PREV_TRACK:
2057 case PICTUREFLOW_PREV_TRACK_REPEAT:
2058 if ( pf_state == pf_show_tracks )
2059 select_prev_track();
2060 break;
2061 #endif
2063 case PICTUREFLOW_SELECT_ALBUM:
2064 if ( pf_state == pf_idle )
2065 pf_state = pf_cover_in;
2066 if ( pf_state == pf_show_tracks )
2067 pf_state = pf_cover_out;
2068 break;
2070 default:
2071 if (rb->default_event_handler_ex(button, cleanup, NULL)
2072 == SYS_USB_CONNECTED)
2073 return PLUGIN_USB_CONNECTED;
2074 break;
2081 /*************************** Plugin entry point ****************************/
2083 enum plugin_status plugin_start(const struct plugin_api *api, const void *parameter)
2085 int ret;
2087 rb = api; /* copy to global api pointer */
2088 (void) parameter;
2089 #if LCD_DEPTH > 1
2090 rb->lcd_set_backdrop(NULL);
2091 #endif
2092 /* Turn off backlight timeout */
2093 backlight_force_on(rb); /* backlight control in lib/helper.c */
2094 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2095 rb->cpu_boost(true);
2096 #endif
2097 ret = main();
2098 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2099 rb->cpu_boost(false);
2100 #endif
2101 if ( ret == PLUGIN_OK ) {
2102 if (!write_pfconfig()) {
2103 rb->splash(HZ, "Error writing config.");
2104 ret = PLUGIN_ERROR;
2108 end_pf_thread();
2109 cleanup(NULL);
2110 return ret;