Fixes FS#8651 (noise and/or crash while crossfading). Latest ARM-asm submit for dsp...
[maemo-rb.git] / apps / plugins / pictureflow.c
blob02f0522f53950ef9491b471e65e1e78ad2551967
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 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 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1497 rb->cpu_boost(false);
1498 #endif
1499 /* Turn on backlight timeout (revert to settings) */
1500 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1502 int i;
1503 for (i = 0; i < slide_cache_in_use; i++) {
1504 rb->bufclose(cache[i].hid);
1506 if ( empty_slide_hid != - 1)
1507 rb->bufclose(empty_slide_hid);
1508 rb->lcd_set_drawmode(old_drawmode);
1512 Create the "?" slide, that is shown while loading
1513 or when no cover was found.
1515 int create_empty_slide(bool force)
1517 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1518 struct bitmap input_bmp;
1519 input_bmp.width = BMPWIDTH_pictureflow_emptyslide;
1520 input_bmp.height = BMPHEIGHT_pictureflow_emptyslide;
1521 input_bmp.format = FORMAT_NATIVE;
1522 input_bmp.data = (char*) &pictureflow_emptyslide;
1523 if ( ! create_bmp(&input_bmp, EMPTY_SLIDE, true) ) return false;
1526 empty_slide_hid = read_pfraw( EMPTY_SLIDE );
1527 if (empty_slide_hid == -1 ) return false;
1529 return true;
1534 Shows the settings menu
1536 int settings_menu(void) {
1537 int selection = 0;
1539 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1540 "Spacing", "Center margin", "Number of slides", "Zoom",
1541 "Rebuild cache");
1543 do {
1544 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1545 switch(selection) {
1546 case 0:
1547 rb->set_bool("Show FPS", &show_fps);
1548 break;
1550 case 1:
1551 rb->set_int("Spacing between slides", "", 1,
1552 &(config.spacing_between_slides),
1553 NULL, 1, 0, 100, NULL );
1554 recalc_table();
1555 reset_slides();
1556 break;
1558 case 2:
1559 rb->set_int("Center margin", "", 1,
1560 &(config.extra_spacing_for_center_slide),
1561 NULL, 1, -50, 50, NULL );
1562 recalc_table();
1563 reset_slides();
1564 break;
1566 case 3:
1567 rb->set_int("Number of slides", "", 1, &(config.show_slides),
1568 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1569 recalc_table();
1570 reset_slides();
1571 break;
1573 case 4:
1574 rb->set_int("Number of slides", "", 1, &(config.zoom),
1575 NULL, 1, 10, 300, NULL );
1576 recalc_table();
1577 reset_slides();
1578 break;
1580 case 5:
1581 rb->remove(CACHE_PREFIX "/ready");
1582 rb->remove(EMPTY_SLIDE);
1583 rb->splash(HZ, "Cache will be rebuilt on next restart");
1584 break;
1586 case MENU_ATTACHED_USB:
1587 return PLUGIN_USB_CONNECTED;
1589 } while ( selection >= 0 );
1590 return 0;
1594 Show the main menu
1596 int main_menu(void)
1598 int selection = 0;
1599 int result;
1601 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1603 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
1604 "Settings", "Return", "Quit");
1606 while (1) {
1607 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
1608 case 0:
1609 result = settings_menu();
1610 if ( result != 0 ) return result;
1611 break;
1613 case 1:
1614 return 0;
1616 case 2:
1617 return -1;
1619 case MENU_ATTACHED_USB:
1620 return PLUGIN_USB_CONNECTED;
1622 default:
1623 return 0;
1629 Fill the config struct with some defaults
1631 void set_default_config(void)
1633 config.spacing_between_slides = 40;
1634 config.extra_spacing_for_center_slide = 0;
1635 config.show_slides = 3;
1636 config.avg_album_width = 0;
1637 config.zoom = 100;
1641 Read the config file.
1642 For now, the size has to match.
1643 Later a version number might be appropiate.
1645 bool read_pfconfig(void)
1647 set_default_config();
1648 /* defaults */
1649 int fh = rb->open( CONFIG_FILE, O_RDONLY );
1650 if ( fh < 0 ) { /* no config yet */
1651 return true;
1653 int ret = rb->read(fh, &config, sizeof(struct config_data));
1654 rb->close(fh);
1655 if ( ret != sizeof(struct config_data) ) {
1656 set_default_config();
1657 rb->splash(2*HZ, "Config invalid. Using defaults");
1659 return true;
1663 Write the config file
1665 bool write_pfconfig(void)
1667 int fh = rb->creat( CONFIG_FILE );
1668 if( fh < 0 ) return false;
1669 rb->write( fh, &config, sizeof( struct config_data ) );
1670 rb->close( fh );
1671 return true;
1675 Animation step for zooming into the current cover
1677 void update_cover_in_animation(void)
1679 cover_animation_keyframe++;
1680 if( cover_animation_keyframe < 20 ) {
1681 center_slide.distance-=5;
1682 center_slide.angle+=1;
1683 extra_fade += 13;
1685 else if( cover_animation_keyframe < 35 ) {
1686 center_slide.angle+=16;
1688 else {
1689 cover_animation_keyframe = 0;
1690 selected_track = 0;
1691 pf_state = pf_show_tracks;
1696 Animation step for zooming out the current cover
1698 void update_cover_out_animation(void)
1700 cover_animation_keyframe++;
1701 if( cover_animation_keyframe <= 15 ) {
1702 center_slide.angle-=16;
1704 else if( cover_animation_keyframe < 35 ) {
1705 center_slide.distance+=5;
1706 center_slide.angle-=1;
1707 extra_fade -= 13;
1709 else {
1710 cover_animation_keyframe = 0;
1711 pf_state = pf_idle;
1716 Draw a blue gradient at y with height h
1718 static inline void draw_gradient(int y, int h)
1720 static int r, inc, c;
1721 inc = (100 << 8) / h;
1722 c = 0;
1723 selected_track_pulse = (selected_track_pulse+1) % 10;
1724 int c2 = selected_track_pulse - 5;
1725 for (r=0; r<h; r++) {
1726 rb->lcd_set_foreground(LCD_RGBPACK(c2+80-(c >> 9), c2+100-(c >> 9),
1727 c2+250-(c >> 8)));
1728 rb->lcd_hline(0, LCD_WIDTH, r+y);
1729 if ( r > h/2 )
1730 c-=inc;
1731 else
1732 c+=inc;
1738 Reset the track list after a album change
1740 void reset_track_list(void)
1742 int albumtxt_w, albumtxt_h;
1743 const char* albumtxt = get_album_name(center_index);
1744 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1745 const int height = LCD_HEIGHT-albumtxt_h-10;
1746 track_list_visible_entries = fmin( height/albumtxt_h , track_count );
1747 start_index_track_list = 0;
1748 track_scroll_index = 0;
1749 track_scroll_dir = 1;
1750 selected_track = 0;
1754 Display the list of tracks
1756 void show_track_list(void)
1758 rb->lcd_clear_display();
1759 if ( center_slide.slide_index != track_index ) {
1760 create_track_index(center_slide.slide_index);
1761 reset_track_list();
1763 static int titletxt_w, titletxt_h, titletxt_y, titletxt_x, i, color;
1764 titletxt_y = 0;
1765 int track_i;
1766 for (i=0; i < track_list_visible_entries; i++) {
1767 track_i = i+start_index_track_list;
1768 rb->lcd_getstringsize(get_track_name(track_i), &titletxt_w, &titletxt_h);
1769 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
1770 if ( track_i == selected_track ) {
1771 draw_gradient(titletxt_y, titletxt_h);
1772 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1773 if (titletxt_w > LCD_WIDTH ) {
1774 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
1775 track_scroll_dir = 1;
1776 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
1777 track_scroll_index += track_scroll_dir*2;
1778 titletxt_x = track_scroll_index;
1780 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1782 else {
1783 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
1784 rb->lcd_set_foreground(LCD_RGBPACK(color,color,color));
1785 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1787 titletxt_y += titletxt_h;
1791 void select_next_track(void)
1793 if ( selected_track < track_count - 1 ) {
1794 selected_track++;
1795 track_scroll_index = 0;
1796 track_scroll_dir = 1;
1797 if (selected_track==(track_list_visible_entries+start_index_track_list))
1798 start_index_track_list++;
1802 void select_prev_track(void)
1804 if (selected_track > 0 ) {
1805 if (selected_track==start_index_track_list) start_index_track_list--;
1806 track_scroll_index = 0;
1807 track_scroll_dir = 1;
1808 selected_track--;
1813 Draw the current album name
1815 void draw_album_text(void)
1817 int albumtxt_w, albumtxt_h;
1818 int albumtxt_y = 0;
1820 char *albumtxt;
1821 int c;
1822 /* Draw album text */
1823 if ( pf_state == pf_scrolling ) {
1824 c = ((slide_frame & 0xffff )/ 255);
1825 if (step < 0) c = 255-c;
1826 if (c > 128 ) { /* half way to next slide .. still not perfect! */
1827 albumtxt = get_album_name(center_index+step);
1828 c = (c-128)*2;
1830 else {
1831 albumtxt = get_album_name(center_index);
1832 c = (128-c)*2;
1835 else {
1836 c= 255;
1837 albumtxt = get_album_name(center_index);
1840 rb->lcd_set_foreground(LCD_RGBPACK(c,c,c));
1841 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1842 if (center_index != prev_center_index) {
1843 albumtxt_x = 0;
1844 albumtxt_dir = -1;
1845 prev_center_index = center_index;
1847 albumtxt_y = LCD_HEIGHT-albumtxt_h-10;
1849 if (albumtxt_w > LCD_WIDTH ) {
1850 rb->lcd_putsxy(albumtxt_x, albumtxt_y , albumtxt);
1851 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
1852 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
1853 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
1854 albumtxt_x += albumtxt_dir;
1857 else {
1858 rb->lcd_putsxy((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
1866 Main function that also contain the main plasma
1867 algorithm.
1869 int main(void)
1871 int ret;
1872 draw_splashscreen();
1874 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
1875 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
1876 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
1877 return PLUGIN_ERROR;
1881 if (!read_pfconfig()) {
1882 rb->splash(HZ, "Error in config. Please delete " CONFIG_FILE);
1883 return PLUGIN_ERROR;
1886 if (!allocate_buffers()) {
1887 rb->splash(HZ, "Could not allocate temporary buffers");
1888 return PLUGIN_ERROR;
1891 ret = create_album_index();
1892 if (ret == ERROR_BUFFER_FULL) {
1893 rb->splash(HZ, "Not enough memory for album names");
1894 return PLUGIN_ERROR;
1895 } else if (ret == ERROR_NO_ALBUMS) {
1896 rb->splash(HZ, "No albums found. Please enable database");
1897 return PLUGIN_ERROR;
1900 if (!create_albumart_cache(config.avg_album_width == 0)) {
1901 rb->splash(HZ, "Could not create album art cache");
1902 return PLUGIN_ERROR;
1905 if (!create_empty_slide(false)) {
1906 rb->splash(HZ, "Could not load the empty slide");
1907 return PLUGIN_ERROR;
1910 if (!free_buffers()) {
1911 rb->splash(HZ, "Could not free temporary buffers");
1912 return PLUGIN_ERROR;
1915 if (!create_pf_thread()) {
1916 rb->splash(HZ, "Cannot create thread!");
1917 return PLUGIN_ERROR;
1920 int i;
1922 /* initialize */
1923 int min_slide_cache = fmin(number_of_slides, SLIDE_CACHE_SIZE);
1924 for (i = 0; i < min_slide_cache; i++) {
1925 cache[i].hid = -1;
1926 cache[i].touched = 0;
1927 slide_cache_stack[i] = SLIDE_CACHE_SIZE-i-1;
1929 slide_cache_stack_index = min_slide_cache-1;
1930 slide_cache_in_use = 0;
1931 buffer = rb->lcd_framebuffer;
1933 pf_state = pf_idle;
1935 track_index = -1;
1936 extra_fade = 0;
1937 center_index = 0;
1938 slide_frame = 0;
1939 step = 0;
1940 target = 0;
1941 fade = 256;
1942 show_fps = false;
1944 recalc_table();
1945 reset_slides();
1947 char fpstxt[10];
1948 int button;
1950 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1951 rb->cpu_boost(true);
1952 #endif
1953 int frames = 0;
1954 long last_update = *rb->current_tick;
1955 long current_update;
1956 long update_interval = 100;
1957 int fps = 0;
1959 bool instant_update;
1960 old_drawmode = rb->lcd_get_drawmode();
1961 rb->lcd_set_drawmode(DRMODE_FG);
1962 while (true) {
1963 current_update = *rb->current_tick;
1964 frames++;
1966 /* Initial rendering */
1967 instant_update = false;
1969 /* Handle states */
1970 switch ( pf_state ) {
1971 case pf_scrolling:
1972 update_scroll_animation();
1973 render_all_slides();
1974 instant_update = true;
1975 break;
1976 case pf_cover_in:
1977 update_cover_in_animation();
1978 render_all_slides();
1979 instant_update = true;
1980 break;
1981 case pf_cover_out:
1982 update_cover_out_animation();
1983 render_all_slides();
1984 instant_update = true;
1985 break;
1986 case pf_show_tracks:
1987 show_track_list();
1988 break;
1989 case pf_idle:
1990 render_all_slides();
1991 break;
1994 /* Calculate FPS */
1995 if (current_update - last_update > update_interval) {
1996 fps = frames * HZ / (current_update - last_update);
1997 last_update = current_update;
1998 frames = 0;
2001 /* Draw FPS */
2002 if (show_fps) {
2003 rb->lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
2004 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2005 rb->lcd_putsxy(0, 0, fpstxt);
2008 draw_album_text();
2011 /* Copy offscreen buffer to LCD and give time to other threads */
2012 rb->lcd_update();
2013 rb->yield();
2015 /*/ Handle buttons */
2016 button = pluginlib_getaction(rb, instant_update ? 0 : HZ/16,
2017 plugin_contexts, NB_ACTION_CONTEXTS);
2019 switch (button) {
2020 case PICTUREFLOW_QUIT:
2021 return PLUGIN_OK;
2023 case PICTUREFLOW_MENU:
2024 if ( pf_state == pf_idle || pf_state == pf_scrolling ) {
2025 ret = main_menu();
2026 if ( ret == -1 ) return PLUGIN_OK;
2027 if ( ret != 0 ) return i;
2028 rb->lcd_set_drawmode(DRMODE_FG);
2030 else {
2031 pf_state = pf_cover_out;
2033 break;
2035 case PICTUREFLOW_NEXT_ALBUM:
2036 case PICTUREFLOW_NEXT_ALBUM_REPEAT:
2037 #ifdef SCROLLWHEEL
2038 if ( pf_state == pf_show_tracks )
2039 select_next_track();
2040 #endif
2041 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2042 show_next_slide();
2043 break;
2045 case PICTUREFLOW_PREV_ALBUM:
2046 case PICTUREFLOW_PREV_ALBUM_REPEAT:
2047 #ifdef SCROLLWHEEL
2048 if ( pf_state == pf_show_tracks )
2049 select_prev_track();
2050 #endif
2051 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2052 show_previous_slide();
2053 break;
2055 #ifndef SCROLLWHEEL
2056 case PICTUREFLOW_NEXT_TRACK:
2057 case PICTUREFLOW_NEXT_TRACK_REPEAT:
2058 if ( pf_state == pf_show_tracks )
2059 select_next_track();
2060 break;
2062 case PICTUREFLOW_PREV_TRACK:
2063 case PICTUREFLOW_PREV_TRACK_REPEAT:
2064 if ( pf_state == pf_show_tracks )
2065 select_prev_track();
2066 break;
2067 #endif
2069 case PICTUREFLOW_SELECT_ALBUM:
2070 if ( pf_state == pf_idle )
2071 pf_state = pf_cover_in;
2072 if ( pf_state == pf_show_tracks )
2073 pf_state = pf_cover_out;
2074 break;
2076 default:
2077 if (rb->default_event_handler_ex(button, cleanup, NULL)
2078 == SYS_USB_CONNECTED)
2079 return PLUGIN_USB_CONNECTED;
2080 break;
2087 /*************************** Plugin entry point ****************************/
2089 enum plugin_status plugin_start(struct plugin_api *api, void *parameter)
2091 int ret;
2093 rb = api; /* copy to global api pointer */
2094 (void) parameter;
2095 #if LCD_DEPTH > 1
2096 rb->lcd_set_backdrop(NULL);
2097 #endif
2098 /* Turn off backlight timeout */
2099 backlight_force_on(rb); /* backlight control in lib/helper.c */
2100 ret = main();
2101 if ( ret == PLUGIN_OK ) {
2102 if (!write_pfconfig()) {
2103 rb->splash(HZ, "Error writing config.");
2104 return PLUGIN_ERROR;
2108 end_pf_thread();
2109 cleanup(NULL);
2110 return ret;