New makefile solution: A single invocation of 'make' to build the entire tree. Fully...
[kugel-rb.git] / apps / plugins / pictureflow.c
blob29e8a749d55e66284b97f924a25908b8469f585a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
26 #include "plugin.h"
27 #include "lib/pluginlib_actions.h"
28 #include "lib/helper.h"
29 #include "lib/bmp.h"
30 #include "lib/picture.h"
31 #include "pluginbitmaps/pictureflow_logo.h"
32 #include "pluginbitmaps/pictureflow_emptyslide.h"
35 PLUGIN_HEADER
37 /******************************* Globals ***********************************/
39 static const struct plugin_api *rb; /* global api struct pointer */
41 const struct button_mapping *plugin_contexts[]
42 = {generic_actions, generic_directions};
44 #define NB_ACTION_CONTEXTS sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
46 /* Key assignement */
47 #if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
48 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
49 || (CONFIG_KEYPAD == IPOD_4G_PAD) \
50 || (CONFIG_KEYPAD == SANSA_E200_PAD)
51 #define SCROLLWHEEL
52 #endif
54 #ifdef SCROLLWHEEL
55 #define PICTUREFLOW_NEXT_ALBUM PLA_DOWN
56 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_DOWN_REPEAT
57 #define PICTUREFLOW_PREV_ALBUM PLA_UP
58 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_UP_REPEAT
59 #else
60 #define PICTUREFLOW_NEXT_ALBUM PLA_RIGHT
61 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_RIGHT_REPEAT
62 #define PICTUREFLOW_PREV_ALBUM PLA_LEFT
63 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_LEFT_REPEAT
64 #define PICTUREFLOW_NEXT_TRACK PLA_DOWN
65 #define PICTUREFLOW_NEXT_TRACK_REPEAT PLA_DOWN_REPEAT
66 #define PICTUREFLOW_PREV_TRACK PLA_UP
67 #define PICTUREFLOW_PREV_TRACK_REPEAT PLA_UP_REPEAT
68 #endif
69 #define PICTUREFLOW_MENU PLA_MENU
70 #define PICTUREFLOW_QUIT PLA_QUIT
71 #define PICTUREFLOW_SELECT_ALBUM PLA_FIRE
74 /* for fixed-point arithmetic, we need minimum 32-bit long
75 long long (64-bit) might be useful for multiplication and division */
76 #define PFreal long
77 #define PFREAL_SHIFT 10
78 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
79 #define PFREAL_ONE (1 << PFREAL_SHIFT)
80 #define PFREAL_HALF (PFREAL_ONE >> 1)
83 #define IANGLE_MAX 1024
84 #define IANGLE_MASK 1023
86 /* maximum size of an slide */
87 #define MAX_IMG_WIDTH LCD_WIDTH
88 #define MAX_IMG_HEIGHT LCD_HEIGHT
90 #if (LCD_HEIGHT < 100)
91 #define PREFERRED_IMG_WIDTH 50
92 #define PREFERRED_IMG_HEIGHT 50
93 #else
94 #define PREFERRED_IMG_WIDTH 100
95 #define PREFERRED_IMG_HEIGHT 100
96 #endif
98 #define BUFFER_WIDTH LCD_WIDTH
99 #define BUFFER_HEIGHT LCD_HEIGHT
101 #define SLIDE_CACHE_SIZE 100
103 #define MAX_SLIDES_COUNT 10
105 #define SPACING_BETWEEN_SLIDE 40
106 #define EXTRA_SPACING_FOR_CENTER_SLIDE 0
108 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
109 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
111 #define EV_EXIT 9999
112 #define EV_WAKEUP 1337
114 /* maximum number of albums */
115 #define MAX_ALBUMS 1024
116 #define AVG_ALBUM_NAME_LENGTH 20
118 #define MAX_TRACKS 50
119 #define AVG_TRACK_NAME_LENGTH 20
122 #define UNIQBUF_SIZE (64*1024)
124 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
125 #define CONFIG_FILE CACHE_PREFIX "/pictureflow.config"
127 /* Error return values */
128 #define ERROR_NO_ALBUMS -1
129 #define ERROR_BUFFER_FULL -2
132 /** structs we use */
134 struct slide_data {
135 int slide_index;
136 int angle;
137 PFreal cx;
138 PFreal cy;
139 PFreal distance;
142 struct slide_cache {
143 int index; /* index of the cached slide */
144 int hid; /* handle ID of the cached slide */
145 long touched; /* last time the slide was touched */
148 struct album_data {
149 int name_idx;
150 long seek;
153 struct track_data {
154 int name_idx;
155 long seek;
158 struct rect {
159 int left;
160 int right;
161 int top;
162 int bottom;
165 struct load_slide_event_data {
166 int slide_index;
167 int cache_index;
171 struct pfraw_header {
172 int32_t width; /* bmap width in pixels */
173 int32_t height; /* bmap height in pixels */
176 const struct picture logos[]={
177 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
180 enum show_album_name_values { album_name_hide = 0, album_name_bottom , album_name_top };
182 struct config_data {
183 long avg_album_width;
184 int spacing_between_slides;
185 int extra_spacing_for_center_slide;
186 int show_slides;
187 int zoom;
188 bool show_fps;
189 bool resize;
190 enum show_album_name_values show_album_name;
193 /** below we allocate the memory we want to use **/
195 static fb_data *buffer; /* for now it always points to the lcd framebuffer */
196 static PFreal rays[BUFFER_WIDTH];
197 static struct slide_data center_slide;
198 static struct slide_data left_slides[MAX_SLIDES_COUNT];
199 static struct slide_data right_slides[MAX_SLIDES_COUNT];
200 static int slide_frame;
201 static int step;
202 static int target;
203 static int fade;
204 static int center_index; /* index of the slide that is in the center */
205 static int itilt;
206 static PFreal offsetX;
207 static PFreal offsetY;
208 static int number_of_slides;
210 static struct slide_cache cache[SLIDE_CACHE_SIZE];
211 static int slide_cache_in_use;
213 /* use long for aligning */
214 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
215 static int slide_cache_stack[SLIDE_CACHE_SIZE]; /* queue (as array) for scheduling load_surface */
216 static int slide_cache_stack_index;
217 struct mutex slide_cache_stack_lock;
219 static int empty_slide_hid;
221 struct thread_entry *thread_id;
222 struct event_queue thread_q;
224 static char tmp_path_name[MAX_PATH];
226 static long uniqbuf[UNIQBUF_SIZE];
227 static struct tagcache_search tcs;
229 static struct album_data album[MAX_ALBUMS];
230 static char album_names[MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH];
231 static int album_count;
233 static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
234 static struct track_data tracks[MAX_TRACKS];
235 static int track_count;
236 static int track_index;
237 static int selected_track;
238 static int selected_track_pulse;
240 static fb_data *input_bmp_buffer;
241 static fb_data *output_bmp_buffer;
242 static int input_hid;
243 static int output_hid;
244 static struct config_data config;
246 static int old_drawmode;
248 static bool thread_is_running;
250 static int cover_animation_keyframe;
251 static int extra_fade;
253 static int albumtxt_x = 0;
254 static int albumtxt_dir = -1;
255 static int prev_center_index = -1;
257 static int start_index_track_list = 0;
258 static int track_list_visible_entries = 0;
259 static int track_scroll_index = 0;
260 static int track_scroll_dir = 1;
263 Proposals for transitions:
265 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
266 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
268 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
270 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
272 TODO:
273 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
274 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
276 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
278 enum pf_states {
279 pf_idle = 0,
280 pf_scrolling,
281 pf_cover_in,
282 pf_show_tracks,
283 pf_cover_out
286 static int pf_state;
288 /** code */
290 bool create_bmp(struct bitmap* input_bmp, char *target_path, bool resize);
291 int load_surface(int);
293 static inline PFreal fmul(PFreal a, PFreal b)
295 return (a*b) >> PFREAL_SHIFT;
298 /* There are some precision issues when not using (long long) which in turn
299 takes very long to compute... I guess the best solution would be to optimize
300 the computations so it only requires a single long */
301 static inline PFreal fdiv(PFreal num, PFreal den)
303 long long p = (long long) (num) << (PFREAL_SHIFT * 2);
304 long long q = p / (long long) den;
305 long long r = q >> PFREAL_SHIFT;
307 return r;
310 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
311 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
312 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
315 #if 0
316 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
317 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
319 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
320 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
322 static inline PFreal fmul(PFreal a, PFreal b)
324 return (a*b) >> PFREAL_SHIFT;
327 static inline PFreal fdiv(PFreal n, PFreal m)
329 return (n<<(PFREAL_SHIFT))/m;
331 #endif
333 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
334 static const PFreal sin_tab[] = {
335 3, 103, 202, 300, 394, 485, 571, 652,
336 726, 793, 853, 904, 947, 980, 1004, 1019,
337 1023, 1018, 1003, 978, 944, 901, 849, 789,
338 721, 647, 566, 479, 388, 294, 196, 97,
339 -4, -104, -203, -301, -395, -486, -572, -653,
340 -727, -794, -854, -905, -948, -981, -1005, -1020,
341 -1024, -1019, -1004, -979, -945, -902, -850, -790,
342 -722, -648, -567, -480, -389, -295, -197, -98,
346 static inline PFreal fsin(int iangle)
348 while(iangle < 0)
349 iangle += IANGLE_MAX;
350 iangle &= IANGLE_MASK;
352 int i = (iangle >> 4);
353 PFreal p = sin_tab[i];
354 PFreal q = sin_tab[(i+1)];
355 PFreal g = (q - p);
356 return p + g * (iangle-i*16)/16;
359 static inline PFreal fcos(int iangle)
361 return fsin(iangle + (IANGLE_MAX >> 2));
365 Create an index of all albums from the database.
366 Also store the album names so we can access them later.
368 int create_album_index(void)
370 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
371 album_count = 0;
372 rb->tagcache_search(&tcs, tag_album);
373 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
374 int l, old_l = 0;
375 album[0].name_idx = 0;
376 while (rb->tagcache_get_next(&tcs) && album_count < MAX_ALBUMS)
378 l = rb->strlen(tcs.result) + 1;
379 if ( album_count > 0 )
380 album[album_count].name_idx = album[album_count-1].name_idx + old_l;
382 if ( (album[album_count].name_idx + l) > MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH )
383 /* not enough memory */
384 return ERROR_BUFFER_FULL;
386 rb->strcpy(album_names + album[album_count].name_idx, tcs.result);
387 album[album_count].seek = tcs.result_seek;
388 old_l = l;
389 album_count++;
391 rb->tagcache_search_finish(&tcs);
393 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
397 Return a pointer to the album name of the given slide_index
399 char* get_album_name(const int slide_index)
401 return album_names + album[slide_index].name_idx;
405 Return a pointer to the track name of the active album
406 create_track_index has to be called first.
408 char* get_track_name(const int track_index)
410 if ( track_index < track_count )
411 return track_names + tracks[track_index].name_idx;
412 return 0;
416 Create the track index of the given slide_index.
418 int create_track_index(const int slide_index)
420 if ( slide_index == track_index ) {
421 return -1;
424 if (!rb->tagcache_search(&tcs, tag_title))
425 return -1;
427 int ret = 0;
428 char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4];
429 int temp_seeks[MAX_TRACKS];
431 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
432 track_count=0;
433 int string_index = 0;
434 int l, track_num, heighest_index = 0;
436 for(l=0;l<MAX_TRACKS;l++)
437 temp_titles[l][0] = '\0';
438 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
440 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
441 if (track_num >= 0)
443 rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]), "%d: %s",
444 track_num+1, tcs.result);
445 temp_seeks[track_num] = tcs.result_seek;
447 else
449 track_num = 0;
450 while (temp_titles[track_num][0] != '\0')
451 track_num++;
452 rb->strcpy(temp_titles[track_num], tcs.result);
453 temp_seeks[track_num] = tcs.result_seek;
455 if (track_num > heighest_index)
456 heighest_index = track_num;
457 track_count++;
460 rb->tagcache_search_finish(&tcs);
461 track_index = slide_index;
463 /* now fix the track list order */
464 l = 0;
465 track_count = 0;
466 while (l <= heighest_index &&
467 string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH)
469 if (temp_titles[l][0] != '\0')
471 rb->strcpy(track_names + string_index, temp_titles[l]);
472 tracks[track_count].name_idx = string_index;
473 tracks[track_count].seek = temp_seeks[l];
474 string_index += rb->strlen(temp_titles[l]) + 1;
475 track_count++;
477 l++;
479 if (ret != 0)
480 return ret;
481 else
482 return (track_count > 0) ? 0 : -1;
487 Determine filename of the album art for the given slide_index and
488 store the result in buf.
489 The algorithm looks for the first track of the given album uses
490 find_albumart to find the filename.
492 bool get_albumart_for_index_from_db(const int slide_index, char *buf, int buflen)
494 if ( slide_index == -1 )
496 rb->strncpy( buf, EMPTY_SLIDE, buflen );
499 if (!rb->tagcache_search(&tcs, tag_filename))
500 return false;
502 bool result;
503 /* find the first track of the album */
504 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
506 if ( rb->tagcache_get_next(&tcs) ) {
507 struct mp3entry id3;
508 char size[9];
509 rb->snprintf(size, sizeof(size), ".%dx%d", PREFERRED_IMG_WIDTH,
510 PREFERRED_IMG_HEIGHT);
511 rb->strncpy( (char*)&id3.path, tcs.result, MAX_PATH );
512 id3.album = get_album_name(slide_index);
513 if ( rb->search_albumart_files(&id3, size, buf, buflen) )
514 result = true;
515 else if ( rb->search_albumart_files(&id3, "", buf, buflen) )
516 result = true;
517 else
518 result = false;
520 else {
521 /* did not find a matching track */
522 result = false;
524 rb->tagcache_search_finish(&tcs);
525 return result;
529 Draw the PictureFlow logo
531 void draw_splashscreen(void)
533 struct screen* display = rb->screens[0];
535 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
536 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
537 rb->lcd_clear_display();
539 const struct picture* logo = &(logos[display->screen_type]);
540 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
542 rb->lcd_update();
547 Draw a simple progress bar
549 void draw_progressbar(int step)
551 int txt_w, txt_h;
552 const int bar_height = 22;
553 const int w = LCD_WIDTH - 20;
554 const int x = 10;
556 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
558 int y = (LCD_HEIGHT - txt_h)/2;
560 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
561 y += (txt_h + 5);
563 rb->lcd_set_foreground(LCD_RGBPACK(100,100,100));
564 rb->lcd_drawrect(x, y, w+2, bar_height);
565 rb->lcd_set_foreground(LCD_RGBPACK(165, 231, 82));
567 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
568 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
569 rb->lcd_update();
570 rb->yield();
574 Allocate temporary buffers
576 bool allocate_buffers(void)
578 int input_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data );
579 int output_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data ) * 2;
581 input_hid = rb->bufalloc(NULL, input_size, TYPE_BITMAP);
583 if (input_hid < 0)
584 return false;
586 if (rb->bufgetdata(input_hid, 0, (void *)&input_bmp_buffer) < input_size) {
587 rb->bufclose(input_hid);
588 return false;
591 output_hid = rb->bufalloc(NULL, output_size, TYPE_BITMAP);
593 if (output_hid < 0) {
594 rb->bufclose(input_hid);
595 return false;
598 if (rb->bufgetdata(output_hid, 0, (void *)&output_bmp_buffer) < output_size) {
599 rb->bufclose(output_hid);
600 return false;
602 return true;
607 Free the temporary buffers
609 bool free_buffers(void)
611 rb->bufclose(input_hid);
612 rb->bufclose(output_hid);
613 return true;
617 Precomupte the album art images and store them in CACHE_PREFIX.
619 bool create_albumart_cache(bool force)
621 number_of_slides = album_count;
622 int fh,ret;
624 if ( ! force && rb->file_exists( CACHE_PREFIX "/ready" ) ) return true;
626 int i, slides = 0;
627 struct bitmap input_bmp;
629 config.avg_album_width = 0;
630 for (i=0; i < album_count; i++)
632 draw_progressbar(i);
633 if (!get_albumart_for_index_from_db(i, tmp_path_name, MAX_PATH))
634 continue;
636 input_bmp.data = (char *)input_bmp_buffer;
637 ret = rb->read_bmp_file(tmp_path_name, &input_bmp,
638 sizeof(fb_data)*MAX_IMG_WIDTH*MAX_IMG_HEIGHT,
639 FORMAT_NATIVE);
640 if (ret <= 0) {
641 rb->splash(HZ, "Could not read bmp");
642 continue; /* skip missing/broken files */
646 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw", i);
647 if (!create_bmp(&input_bmp, tmp_path_name, false)) {
648 rb->splash(HZ, "Could not write bmp");
650 config.avg_album_width += input_bmp.width;
651 slides++;
652 if ( rb->button_get(false) == PICTUREFLOW_MENU ) return false;
654 if ( slides == 0 ) {
655 rb->splash(2*HZ, "No albums found");
656 return false;
658 config.avg_album_width /= slides;
659 if ( config.avg_album_width == 0 ) {
660 rb->splash(HZ, "album size is 0");
661 return false;
663 fh = rb->creat( CACHE_PREFIX "/ready" );
664 rb->close(fh);
665 return true;
669 Return the index on the stack of slide_index.
670 Return -1 if slide_index is not on the stack.
672 static inline int slide_stack_get_index(const int slide_index)
674 int i = slide_cache_stack_index + 1;
675 while (i--) {
676 if ( slide_cache_stack[i] == slide_index ) return i;
678 return -1;
682 Push the slide_index on the stack so the image will be loaded.
683 The algorithm tries to keep the center_index on top and the
684 slide_index as high as possible (so second if center_index is
685 on the stack).
687 void slide_stack_push(const int slide_index)
689 rb->mutex_lock(&slide_cache_stack_lock);
691 if ( slide_cache_stack_index == -1 ) {
692 /* empty stack, no checks at all */
693 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
694 rb->mutex_unlock(&slide_cache_stack_lock);
695 return;
698 int i = slide_stack_get_index( slide_index );
700 if ( i == slide_cache_stack_index ) {
701 /* slide_index is on top, so we do not change anything */
702 rb->mutex_unlock(&slide_cache_stack_lock);
703 return;
706 if ( i >= 0 ) {
707 /* slide_index is already on the stack, but not on top */
708 int tmp = slide_cache_stack[ slide_cache_stack_index ];
709 if ( tmp == center_index ) {
710 /* the center_index is on top of the stack so do not touch that */
711 if ( slide_cache_stack_index > 0 ) {
712 /* but maybe it is possible to swap the given slide_index to the second place */
713 tmp = slide_cache_stack[ slide_cache_stack_index -1 ];
714 slide_cache_stack[ slide_cache_stack_index - 1 ] = slide_cache_stack[ i ];
715 slide_cache_stack[ i ] = tmp;
718 else {
719 /* if the center_index is not on top (i.e. already loaded) bring the slide_index to the top */
720 slide_cache_stack[ slide_cache_stack_index ] = slide_cache_stack[ i ];
721 slide_cache_stack[ i ] = tmp;
724 else {
725 /* slide_index is not on the stack */
726 if ( slide_cache_stack_index >= SLIDE_CACHE_SIZE-1 ) {
727 /* if we exceeded the stack size, clear the first half of the stack */
728 slide_cache_stack_index = SLIDE_CACHE_SIZE/2;
729 for (i = 0; i <= slide_cache_stack_index ; i++)
730 slide_cache_stack[ i ] = slide_cache_stack[ i + slide_cache_stack_index ];
732 if ( slide_cache_stack[ slide_cache_stack_index ] == center_index ) {
733 /* if the center_index is on top leave it there */
734 slide_cache_stack[ slide_cache_stack_index ] = slide_index;
735 slide_cache_stack[ ++slide_cache_stack_index ] = center_index;
737 else {
738 /* usual stack case: push the slide_index on top */
739 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
742 rb->mutex_unlock(&slide_cache_stack_lock);
747 Pop the topmost item from the stack and decrease the stack size
749 static inline int slide_stack_pop(void)
751 rb->mutex_lock(&slide_cache_stack_lock);
752 int result;
753 if ( slide_cache_stack_index >= 0 )
754 result = slide_cache_stack[ slide_cache_stack_index-- ];
755 else
756 result = -1;
757 rb->mutex_unlock(&slide_cache_stack_lock);
758 return result;
763 Load the slide into the cache.
764 Thus we have to queue the loading request in our thread while discarding the
765 oldest slide.
767 static inline void request_surface(const int slide_index)
769 slide_stack_push(slide_index);
770 rb->queue_post(&thread_q, EV_WAKEUP, 0);
775 Thread used for loading and preparing bitmaps in the background
777 void thread(void)
779 long sleep_time = 5 * HZ;
780 struct queue_event ev;
781 while (1) {
782 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
783 switch (ev.id) {
784 case EV_EXIT:
785 return;
786 case EV_WAKEUP:
787 /* we just woke up */
788 break;
790 int slide_index;
791 while ( (slide_index = slide_stack_pop()) != -1 ) {
792 load_surface( slide_index );
793 rb->queue_wait_w_tmo(&thread_q, &ev, HZ/10);
794 switch (ev.id) {
795 case EV_EXIT:
796 return;
804 End the thread by posting the EV_EXIT event
806 void end_pf_thread(void)
808 if ( thread_is_running ) {
809 rb->queue_post(&thread_q, EV_EXIT, 0);
810 rb->thread_wait(thread_id);
811 /* remove the thread's queue from the broadcast list */
812 rb->queue_delete(&thread_q);
813 thread_is_running = false;
820 Create the thread an setup the event queue
822 bool create_pf_thread(void)
824 rb->queue_init(&thread_q, true); /* put the thread's queue in the bcast list */
825 if ((thread_id = rb->create_thread(
826 thread,
827 thread_stack,
828 sizeof(thread_stack),
830 "Picture load thread"
831 IF_PRIO(, PRIORITY_BACKGROUND)
832 IF_COP(, CPU)
834 ) == NULL) {
835 return false;
837 thread_is_running = true;
838 return true;
842 Safe the given bitmap as filename in the pfraw format
844 bool save_pfraw(char* filename, struct bitmap *bm)
846 struct pfraw_header bmph;
847 bmph.width = bm->width;
848 bmph.height = bm->height;
849 int fh = rb->creat( filename );
850 if( fh < 0 ) return false;
851 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
852 int y;
853 for( y = 0; y < bm->height; y++ )
855 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
856 rb->write( fh, d, sizeof( fb_data ) * bm->width );
858 rb->close( fh );
859 return true;
864 Read the pfraw image given as filename and return the hid of the buffer
866 int read_pfraw(char* filename)
868 struct pfraw_header bmph;
869 int fh = rb->open(filename, O_RDONLY);
870 rb->read(fh, &bmph, sizeof(struct pfraw_header));
871 if( fh < 0 ) {
872 return empty_slide_hid;
875 int size = sizeof(struct bitmap) + sizeof( fb_data ) * bmph.width * bmph.height;
877 int hid = rb->bufalloc(NULL, size, TYPE_BITMAP);
878 if (hid < 0) {
879 rb->close( fh );
880 return -1;
883 struct bitmap *bm;
884 if (rb->bufgetdata(hid, 0, (void *)&bm) < size) {
885 rb->close( fh );
886 return -1;
889 bm->width = bmph.width;
890 bm->height = bmph.height;
891 bm->format = FORMAT_NATIVE;
892 bm->data = ((unsigned char *)bm + sizeof(struct bitmap));
894 int y;
895 for( y = 0; y < bm->height; y++ )
897 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
898 rb->read( fh, d , sizeof( fb_data ) * bm->width );
900 rb->close( fh );
901 return hid;
906 Create the slide with its reflection for the given slide_index and filename
907 and store it as pfraw in CACHE_PREFIX/[slide_index].pfraw
909 bool create_bmp(struct bitmap *input_bmp, char *target_path, bool resize)
911 struct bitmap output_bmp;
913 output_bmp.format = input_bmp->format;
914 output_bmp.data = (char *)output_bmp_buffer;
916 if ( resize ) {
917 /* resize image */
918 output_bmp.width = config.avg_album_width;
919 output_bmp.height = config.avg_album_width;
920 smooth_resize_bitmap(input_bmp, &output_bmp);
922 /* Resized bitmap is now in the output buffer,
923 copy it back to the input buffer */
924 rb->memcpy(input_bmp_buffer, output_bmp_buffer,
925 config.avg_album_width * config.avg_album_width * sizeof(fb_data));
926 input_bmp->data = (char *)input_bmp_buffer;
927 input_bmp->width = output_bmp.width;
928 input_bmp->height = output_bmp.height;
931 output_bmp.width = input_bmp->width * 2;
932 output_bmp.height = input_bmp->height;
934 fb_data *src = (fb_data *)input_bmp->data;
935 fb_data *dst = (fb_data *)output_bmp.data;
937 /* transpose the image, this is to speed-up the rendering
938 because we process one column at a time
939 (and much better and faster to work row-wise, i.e in one scanline) */
940 int hofs = input_bmp->width / 3;
941 rb->memset(dst, 0, sizeof(fb_data) * output_bmp.width * output_bmp.height);
942 int x, y;
943 for (x = 0; x < input_bmp->width; x++)
944 for (y = 0; y < input_bmp->height; y++)
945 dst[output_bmp.width * x + (hofs + y)] =
946 src[y * input_bmp->width + x];
948 /* create the reflection */
949 int ht = input_bmp->height - hofs;
950 int hte = ht;
951 for (x = 0; x < input_bmp->width; x++) {
952 for (y = 0; y < ht; y++) {
953 fb_data color = src[x + input_bmp->width * (input_bmp->height - y - 1)];
954 int r = RGB_UNPACK_RED(color) * (hte - y) / hte * 3 / 5;
955 int g = RGB_UNPACK_GREEN(color) * (hte - y) / hte * 3 / 5;
956 int b = RGB_UNPACK_BLUE(color) * (hte - y) / hte * 3 / 5;
957 dst[output_bmp.height + hofs + y + output_bmp.width * x] =
958 LCD_RGBPACK(r, g, b);
961 return save_pfraw(target_path, &output_bmp);
966 Load the surface for the given slide_index into the cache at cache_index.
968 static inline bool load_and_prepare_surface(const int slide_index,
969 const int cache_index)
971 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
972 slide_index);
974 int hid = read_pfraw(tmp_path_name);
975 if (hid < 0)
976 return false;
978 cache[cache_index].hid = hid;
980 if ( cache_index < SLIDE_CACHE_SIZE ) {
981 cache[cache_index].index = slide_index;
982 cache[cache_index].touched = *rb->current_tick;
985 return true;
990 Load the surface from a bmp and overwrite the oldest slide in the cache
991 if necessary.
993 int load_surface(const int slide_index)
995 long oldest_tick = *rb->current_tick;
996 int oldest_slide = 0;
997 int i;
998 if ( slide_cache_in_use < SLIDE_CACHE_SIZE ) { /* initial fill */
999 oldest_slide = slide_cache_in_use;
1000 load_and_prepare_surface(slide_index, slide_cache_in_use++);
1002 else {
1003 for (i = 0; i < SLIDE_CACHE_SIZE; i++) { /* look for oldest slide */
1004 if (cache[i].touched < oldest_tick) {
1005 oldest_slide = i;
1006 oldest_tick = cache[i].touched;
1009 if (cache[oldest_slide].hid != empty_slide_hid) {
1010 rb->bufclose(cache[oldest_slide].hid);
1011 cache[oldest_slide].hid = -1;
1013 load_and_prepare_surface(slide_index, oldest_slide);
1015 return oldest_slide;
1020 Get a slide from the buffer
1022 static inline struct bitmap *get_slide(const int hid)
1024 if (hid < 0)
1025 return NULL;
1027 struct bitmap *bmp;
1029 ssize_t ret = rb->bufgetdata(hid, 0, (void *)&bmp);
1030 if (ret < 0)
1031 return NULL;
1033 return bmp;
1038 Return the requested surface
1040 static inline struct bitmap *surface(const int slide_index)
1042 if (slide_index < 0)
1043 return 0;
1044 if (slide_index >= number_of_slides)
1045 return 0;
1047 int i;
1048 for (i = 0; i < slide_cache_in_use; i++) { /* maybe do the inverse mapping => implies dynamic allocation? */
1049 if ( cache[i].index == slide_index ) {
1050 /* We have already loaded our slide, so touch it and return it. */
1051 cache[i].touched = *rb->current_tick;
1052 return get_slide(cache[i].hid);
1055 request_surface(slide_index);
1056 return get_slide(empty_slide_hid);
1060 adjust slides so that they are in "steady state" position
1062 void reset_slides(void)
1064 center_slide.angle = 0;
1065 center_slide.cx = 0;
1066 center_slide.cy = 0;
1067 center_slide.distance = 0;
1068 center_slide.slide_index = center_index;
1070 int i;
1071 for (i = 0; i < config.show_slides; i++) {
1072 struct slide_data *si = &left_slides[i];
1073 si->angle = itilt;
1074 si->cx = -(offsetX + config.spacing_between_slides * i * PFREAL_ONE);
1075 si->cy = offsetY;
1076 si->slide_index = center_index - 1 - i;
1077 si->distance = 0;
1080 for (i = 0; i < config.show_slides; i++) {
1081 struct slide_data *si = &right_slides[i];
1082 si->angle = -itilt;
1083 si->cx = offsetX + config.spacing_between_slides * i * PFREAL_ONE;
1084 si->cy = offsetY;
1085 si->slide_index = center_index + 1 + i;
1086 si->distance = 0;
1092 Updates look-up table and other stuff necessary for the rendering.
1093 Call this when the viewport size or slide dimension is changed.
1095 void recalc_table(void)
1097 int w = (BUFFER_WIDTH + 1) / 2;
1098 int h = (BUFFER_HEIGHT + 1) / 2;
1099 int i;
1100 for (i = 0; i < w; i++) {
1101 PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2 * h);
1102 rays[w - i - 1] = -gg;
1103 rays[w + i] = gg;
1106 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1108 offsetX = config.avg_album_width / 2 * (PFREAL_ONE - fcos(itilt));
1109 offsetY = config.avg_album_width / 2 * fsin(itilt);
1110 offsetX += config.avg_album_width * PFREAL_ONE;
1111 offsetY += config.avg_album_width * PFREAL_ONE / 4;
1112 offsetX += config.extra_spacing_for_center_slide << PFREAL_SHIFT;
1117 Fade the given color by spreading the fb_data (ushort)
1118 to an uint, multiply and compress the result back to a ushort.
1120 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1121 static inline fb_data fade_color(fb_data c, unsigned int a)
1123 c = swap16(c);
1124 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1125 return swap16( (fb_data) (p | ( p >> 16 )) );
1127 #else
1128 static inline fb_data fade_color(fb_data c, unsigned int a)
1130 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1131 return (p | ( p >> 16 ));
1133 #endif
1139 Render a single slide
1141 void render_slide(struct slide_data *slide, struct rect *result_rect,
1142 const int alpha, int col1, int col2)
1144 rb->memset(result_rect, 0, sizeof(struct rect));
1145 struct bitmap *bmp = surface(slide->slide_index);
1146 if (!bmp) {
1147 return;
1149 fb_data *src = (fb_data *)bmp->data;
1151 const int sw = bmp->height;
1152 const int sh = bmp->width;
1154 const int h = LCD_HEIGHT;
1155 const int w = LCD_WIDTH;
1157 if (col1 > col2) {
1158 int c = col2;
1159 col2 = col1;
1160 col1 = c;
1163 col1 = (col1 >= 0) ? col1 : 0;
1164 col2 = (col2 >= 0) ? col2 : w - 1;
1165 col1 = fmin(col1, w - 1);
1166 col2 = fmin(col2, w - 1);
1168 int distance = (h + slide->distance) * 100 / config.zoom;
1169 if (distance < 100 ) distance = 100; /* clamp distances */
1170 PFreal sdx = fcos(slide->angle);
1171 PFreal sdy = fsin(slide->angle);
1172 PFreal xs = slide->cx - bmp->width * sdx / 4;
1173 PFreal ys = slide->cy - bmp->width * sdy / 4;
1174 PFreal dist = distance * PFREAL_ONE;
1176 const int alpha4 = alpha >> 3;
1178 int xi = fmax((PFreal) 0,
1179 ((w * PFREAL_ONE / 2) +
1180 fdiv(xs * h, dist + ys)) >> PFREAL_SHIFT);
1181 if (xi >= w) {
1182 return;
1185 bool flag = false;
1186 result_rect->left = xi;
1187 int x;
1188 for (x = fmax(xi, col1); x <= col2; x++) {
1189 PFreal hity = 0;
1190 PFreal fk = rays[x];
1191 if (sdy) {
1192 fk = fk - fdiv(sdx, sdy);
1193 hity = -fdiv(( rays[x] * distance
1194 - slide->cx
1195 + slide->cy * sdx / sdy), fk);
1198 dist = distance * PFREAL_ONE + hity;
1199 if (dist < 0)
1200 continue;
1202 PFreal hitx = fmul(dist, rays[x]);
1204 PFreal hitdist = fdiv(hitx - slide->cx, sdx);
1206 const int column = (sw >> 1) + (hitdist >> PFREAL_SHIFT);
1207 if (column >= sw)
1208 break;
1210 if (column < 0)
1211 continue;
1213 result_rect->right = x;
1214 if (!flag)
1215 result_rect->left = x;
1216 flag = true;
1218 int y1 = (h >> 1);
1219 int y2 = y1 + 1;
1220 fb_data *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
1221 fb_data *pixel2 = &buffer[y2 * BUFFER_WIDTH + x];
1222 const int pixelstep = pixel2 - pixel1;
1224 int center = (sh >> 1);
1225 int dy = dist / h;
1226 int p1 = center * PFREAL_ONE - (dy >> 2);
1227 int p2 = center * PFREAL_ONE + (dy >> 2);
1229 const fb_data *ptr = &src[column * bmp->width];
1231 if (alpha == 256)
1232 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1233 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
1234 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
1235 p1 -= dy;
1236 p2 += dy;
1237 y1--;
1238 y2++;
1239 pixel1 -= pixelstep;
1240 pixel2 += pixelstep;
1241 } else
1242 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1243 *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT], alpha4);
1244 *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT], alpha4);
1245 p1 -= dy;
1246 p2 += dy;
1247 y1--;
1248 y2++;
1249 pixel1 -= pixelstep;
1250 pixel2 += pixelstep;
1253 /* let the music play... */
1254 rb->yield();
1256 result_rect->top = 0;
1257 result_rect->bottom = h - 1;
1258 return;
1263 Jump the the given slide_index
1265 static inline void set_current_slide(const int slide_index)
1267 step = 0;
1268 center_index = fbound(slide_index, 0, number_of_slides - 1);
1269 target = center_index;
1270 slide_frame = slide_index << 16;
1271 reset_slides();
1275 Start the animation for changing slides
1277 void start_animation(void)
1279 step = (target < center_slide.slide_index) ? -1 : 1;
1280 pf_state = pf_scrolling;
1284 Go to the previous slide
1286 void show_previous_slide(void)
1288 if (step == 0) {
1289 if (center_index > 0) {
1290 target = center_index - 1;
1291 start_animation();
1293 } else if ( step > 0 ) {
1294 target = center_index;
1295 start_animation();
1296 } else {
1297 target = fmax(0, center_index - 2);
1303 Go to the next slide
1305 void show_next_slide(void)
1307 if (step == 0) {
1308 if (center_index < number_of_slides - 1) {
1309 target = center_index + 1;
1310 start_animation();
1312 } else if ( step < 0 ) {
1313 target = center_index;
1314 start_animation();
1315 } else {
1316 target = fmin(center_index + 2, number_of_slides - 1);
1322 Return true if the rect has size 0
1324 static inline bool is_empty_rect(struct rect *r)
1326 return ((r->left == 0) && (r->right == 0) && (r->top == 0)
1327 && (r->bottom == 0));
1332 Render the slides. Updates only the offscreen buffer.
1334 void render_all_slides(void)
1336 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
1337 rb->lcd_clear_display(); /* TODO: Optimizes this by e.g. invalidating rects */
1339 int nleft = config.show_slides;
1340 int nright = config.show_slides;
1342 struct rect r;
1343 r.left = LCD_WIDTH; r.top = 0; r.bottom = 0; r.right = 0;
1344 render_slide(&center_slide, &r, 256, -1, -1);
1345 #ifdef DEBUG_DRAW
1346 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1347 #endif
1348 int c1 = r.left;
1349 int c2 = r.right;
1350 int index;
1351 if (step == 0) {
1352 /* no animation, boring plain rendering */
1353 for (index = 0; index < nleft - 1; index++) {
1354 int alpha = (index < nleft - 2) ? 256 : 128;
1355 alpha -= extra_fade;
1356 if (alpha < 0 ) alpha = 0;
1357 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1358 if (!is_empty_rect(&r)) {
1359 #ifdef DEBUG_DRAW
1360 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1361 #endif
1362 c1 = r.left;
1365 for (index = 0; index < nright - 1; index++) {
1366 int alpha = (index < nright - 2) ? 256 : 128;
1367 alpha -= extra_fade;
1368 if (alpha < 0 ) alpha = 0;
1369 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1370 BUFFER_WIDTH);
1371 if (!is_empty_rect(&r)) {
1372 #ifdef DEBUG_DRAW
1373 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1374 #endif
1375 c2 = r.right;
1378 } else {
1379 if ( step < 0 ) c1 = BUFFER_WIDTH;
1380 /* the first and last slide must fade in/fade out */
1381 for (index = 0; index < nleft; index++) {
1382 int alpha = 256;
1383 if (index == nleft - 1)
1384 alpha = (step > 0) ? 0 : 128 - fade / 2;
1385 if (index == nleft - 2)
1386 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1387 if (index == nleft - 3)
1388 alpha = (step > 0) ? 256 - fade / 2 : 256;
1389 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1391 if (!is_empty_rect(&r)) {
1392 #ifdef DEBUG_DRAW
1393 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1394 #endif
1395 c1 = r.left;
1398 if ( step > 0 ) c2 = 0;
1399 for (index = 0; index < nright; index++) {
1400 int alpha = (index < nright - 2) ? 256 : 128;
1401 if (index == nright - 1)
1402 alpha = (step > 0) ? fade / 2 : 0;
1403 if (index == nright - 2)
1404 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1405 if (index == nright - 3)
1406 alpha = (step > 0) ? 256 : 128 + fade / 2;
1407 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1408 BUFFER_WIDTH);
1409 if (!is_empty_rect(&r)) {
1410 #ifdef DEBUG_DRAW
1411 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1412 #endif
1413 c2 = r.right;
1421 Updates the animation effect. Call this periodically from a timer.
1423 void update_scroll_animation(void)
1425 if (step == 0)
1426 return;
1428 int speed = 16384;
1429 int i;
1431 /* deaccelerate when approaching the target */
1432 if (true) {
1433 const int max = 2 * 65536;
1435 int fi = slide_frame;
1436 fi -= (target << 16);
1437 if (fi < 0)
1438 fi = -fi;
1439 fi = fmin(fi, max);
1441 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1442 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1445 slide_frame += speed * step;
1447 int index = slide_frame >> 16;
1448 int pos = slide_frame & 0xffff;
1449 int neg = 65536 - pos;
1450 int tick = (step < 0) ? neg : pos;
1451 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1453 /* the leftmost and rightmost slide must fade away */
1454 fade = pos / 256;
1456 if (step < 0)
1457 index++;
1458 if (center_index != index) {
1459 center_index = index;
1460 slide_frame = index << 16;
1461 center_slide.slide_index = center_index;
1462 for (i = 0; i < config.show_slides; i++)
1463 left_slides[i].slide_index = center_index - 1 - i;
1464 for (i = 0; i < config.show_slides; i++)
1465 right_slides[i].slide_index = center_index + 1 + i;
1468 center_slide.angle = (step * tick * itilt) >> 16;
1469 center_slide.cx = -step * fmul(offsetX, ftick);
1470 center_slide.cy = fmul(offsetY, ftick);
1472 if (center_index == target) {
1473 reset_slides();
1474 pf_state = pf_idle;
1475 step = 0;
1476 fade = 256;
1477 return;
1480 for (i = 0; i < config.show_slides; i++) {
1481 struct slide_data *si = &left_slides[i];
1482 si->angle = itilt;
1483 si->cx =
1484 -(offsetX + config.spacing_between_slides * i * PFREAL_ONE + step
1485 * config.spacing_between_slides * ftick);
1486 si->cy = offsetY;
1489 for (i = 0; i < config.show_slides; i++) {
1490 struct slide_data *si = &right_slides[i];
1491 si->angle = -itilt;
1492 si->cx =
1493 offsetX + config.spacing_between_slides * i * PFREAL_ONE - step
1494 * config.spacing_between_slides * ftick;
1495 si->cy = offsetY;
1498 if (step > 0) {
1499 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1500 right_slides[0].angle = -(neg * itilt) >> 16;
1501 right_slides[0].cx = fmul(offsetX, ftick);
1502 right_slides[0].cy = fmul(offsetY, ftick);
1503 } else {
1504 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1505 left_slides[0].angle = (pos * itilt) >> 16;
1506 left_slides[0].cx = -fmul(offsetX, ftick);
1507 left_slides[0].cy = fmul(offsetY, ftick);
1510 /* must change direction ? */
1511 if (target < index)
1512 if (step > 0)
1513 step = -1;
1514 if (target > index)
1515 if (step < 0)
1516 step = 1;
1521 Cleanup the plugin
1523 void cleanup(void *parameter)
1525 (void) parameter;
1526 /* Turn on backlight timeout (revert to settings) */
1527 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1529 int i;
1530 for (i = 0; i < slide_cache_in_use; i++) {
1531 rb->bufclose(cache[i].hid);
1533 if ( empty_slide_hid != - 1)
1534 rb->bufclose(empty_slide_hid);
1535 rb->lcd_set_drawmode(old_drawmode);
1539 Create the "?" slide, that is shown while loading
1540 or when no cover was found.
1542 int create_empty_slide(bool force)
1544 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1545 struct bitmap input_bmp;
1546 input_bmp.width = BMPWIDTH_pictureflow_emptyslide;
1547 input_bmp.height = BMPHEIGHT_pictureflow_emptyslide;
1548 input_bmp.format = FORMAT_NATIVE;
1549 input_bmp.data = (char*) &pictureflow_emptyslide;
1550 if ( ! create_bmp(&input_bmp, EMPTY_SLIDE, true) ) return false;
1553 empty_slide_hid = read_pfraw( EMPTY_SLIDE );
1554 if (empty_slide_hid == -1 ) return false;
1556 return true;
1561 Shows the settings menu
1563 int settings_menu(void) {
1564 int selection = 0;
1566 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1567 "Spacing", "Center margin", "Number of slides", "Zoom",
1568 "Rebuild cache");
1570 do {
1571 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1572 switch(selection) {
1573 case 0:
1574 rb->set_bool("Show FPS", &(config.show_fps));
1575 break;
1577 case 1:
1578 rb->set_int("Spacing between slides", "", 1,
1579 &(config.spacing_between_slides),
1580 NULL, 1, 0, 100, NULL );
1581 recalc_table();
1582 reset_slides();
1583 break;
1585 case 2:
1586 rb->set_int("Center margin", "", 1,
1587 &(config.extra_spacing_for_center_slide),
1588 NULL, 1, -50, 50, NULL );
1589 recalc_table();
1590 reset_slides();
1591 break;
1593 case 3:
1594 rb->set_int("Number of slides", "", 1, &(config.show_slides),
1595 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1596 recalc_table();
1597 reset_slides();
1598 break;
1600 case 4:
1601 rb->set_int("Zoom", "", 1, &(config.zoom),
1602 NULL, 1, 10, 300, NULL );
1603 recalc_table();
1604 reset_slides();
1605 break;
1606 case 5:
1607 rb->remove(CACHE_PREFIX "/ready");
1608 rb->remove(EMPTY_SLIDE);
1609 rb->splash(HZ, "Cache will be rebuilt on next restart");
1610 break;
1612 case MENU_ATTACHED_USB:
1613 return PLUGIN_USB_CONNECTED;
1615 } while ( selection >= 0 );
1616 return 0;
1620 Show the main menu
1622 int main_menu(void)
1624 int selection = 0;
1625 int result;
1627 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1629 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
1630 "Settings", "Return", "Quit");
1632 while (1) {
1633 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
1634 case 0:
1635 result = settings_menu();
1636 if ( result != 0 ) return result;
1637 break;
1639 case 1:
1640 return 0;
1642 case 2:
1643 return -1;
1645 case MENU_ATTACHED_USB:
1646 return PLUGIN_USB_CONNECTED;
1648 default:
1649 return 0;
1655 Fill the config struct with some defaults
1657 void set_default_config(void)
1659 config.spacing_between_slides = 40;
1660 config.extra_spacing_for_center_slide = 0;
1661 config.show_slides = 3;
1662 config.avg_album_width = 0;
1663 config.zoom = 100;
1664 config.show_fps = false;
1665 config.resize = true;
1666 config.show_album_name = album_name_bottom;
1670 Read the config file.
1671 For now, the size has to match.
1672 Later a version number might be appropiate.
1674 bool read_pfconfig(void)
1676 set_default_config();
1677 /* defaults */
1678 int fh = rb->open( CONFIG_FILE, O_RDONLY );
1679 if ( fh < 0 ) { /* no config yet */
1680 return true;
1682 int ret = rb->read(fh, &config, sizeof(struct config_data));
1683 rb->close(fh);
1684 if ( ret != sizeof(struct config_data) ) {
1685 set_default_config();
1686 rb->splash(2*HZ, "Config invalid. Using defaults");
1688 return true;
1692 Write the config file
1694 bool write_pfconfig(void)
1696 int fh = rb->creat( CONFIG_FILE );
1697 if( fh < 0 ) return false;
1698 rb->write( fh, &config, sizeof( struct config_data ) );
1699 rb->close( fh );
1700 return true;
1704 Animation step for zooming into the current cover
1706 void update_cover_in_animation(void)
1708 cover_animation_keyframe++;
1709 if( cover_animation_keyframe < 20 ) {
1710 center_slide.distance-=5;
1711 center_slide.angle+=1;
1712 extra_fade += 13;
1714 else if( cover_animation_keyframe < 35 ) {
1715 center_slide.angle+=16;
1717 else {
1718 cover_animation_keyframe = 0;
1719 selected_track = 0;
1720 pf_state = pf_show_tracks;
1725 Animation step for zooming out the current cover
1727 void update_cover_out_animation(void)
1729 cover_animation_keyframe++;
1730 if( cover_animation_keyframe <= 15 ) {
1731 center_slide.angle-=16;
1733 else if( cover_animation_keyframe < 35 ) {
1734 center_slide.distance+=5;
1735 center_slide.angle-=1;
1736 extra_fade -= 13;
1738 else {
1739 cover_animation_keyframe = 0;
1740 pf_state = pf_idle;
1745 Draw a blue gradient at y with height h
1747 static inline void draw_gradient(int y, int h)
1749 static int r, inc, c;
1750 inc = (100 << 8) / h;
1751 c = 0;
1752 selected_track_pulse = (selected_track_pulse+1) % 10;
1753 int c2 = selected_track_pulse - 5;
1754 for (r=0; r<h; r++) {
1755 rb->lcd_set_foreground(LCD_RGBPACK(c2+80-(c >> 9), c2+100-(c >> 9),
1756 c2+250-(c >> 8)));
1757 rb->lcd_hline(0, LCD_WIDTH, r+y);
1758 if ( r > h/2 )
1759 c-=inc;
1760 else
1761 c+=inc;
1767 Reset the track list after a album change
1769 void reset_track_list(void)
1771 int albumtxt_w, albumtxt_h;
1772 const char* albumtxt = get_album_name(center_index);
1773 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1774 const int height =
1775 LCD_HEIGHT-albumtxt_h-10 - (config.show_fps?(albumtxt_h + 5):0);
1776 track_list_visible_entries = fmin( height/albumtxt_h , track_count );
1777 start_index_track_list = 0;
1778 track_scroll_index = 0;
1779 track_scroll_dir = 1;
1780 selected_track = 0;
1784 Display the list of tracks
1786 void show_track_list(void)
1788 rb->lcd_clear_display();
1789 if ( center_slide.slide_index != track_index ) {
1790 create_track_index(center_slide.slide_index);
1791 reset_track_list();
1793 static int titletxt_w, titletxt_h, titletxt_y, titletxt_x, i, color;
1794 rb->lcd_getstringsize("W", NULL, &titletxt_h);
1795 if (track_list_visible_entries >= track_count)
1797 int albumtxt_h;
1798 const char* albumtxt = get_album_name(center_index);
1799 rb->lcd_getstringsize(albumtxt, NULL, &albumtxt_h);
1800 titletxt_y = ((LCD_HEIGHT-albumtxt_h-10)-(track_count*albumtxt_h))/2;
1802 else if (config.show_fps)
1803 titletxt_y = titletxt_h + 5;
1804 else
1805 titletxt_y = 0;
1807 int track_i;
1808 for (i=0; i < track_list_visible_entries; i++) {
1809 track_i = i+start_index_track_list;
1810 rb->lcd_getstringsize(get_track_name(track_i), &titletxt_w, NULL);
1811 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
1812 if ( track_i == selected_track ) {
1813 draw_gradient(titletxt_y, titletxt_h);
1814 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1815 if (titletxt_w > LCD_WIDTH ) {
1816 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
1817 track_scroll_dir = 1;
1818 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
1819 track_scroll_index += track_scroll_dir*2;
1820 titletxt_x = track_scroll_index;
1822 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1824 else {
1825 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
1826 rb->lcd_set_foreground(LCD_RGBPACK(color,color,color));
1827 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1829 titletxt_y += titletxt_h;
1833 void select_next_track(void)
1835 if ( selected_track < track_count - 1 ) {
1836 selected_track++;
1837 track_scroll_index = 0;
1838 track_scroll_dir = 1;
1839 if (selected_track==(track_list_visible_entries+start_index_track_list))
1840 start_index_track_list++;
1844 void select_prev_track(void)
1846 if (selected_track > 0 ) {
1847 if (selected_track==start_index_track_list) start_index_track_list--;
1848 track_scroll_index = 0;
1849 track_scroll_dir = 1;
1850 selected_track--;
1855 Draw the current album name
1857 void draw_album_text(void)
1859 int albumtxt_w, albumtxt_h;
1860 int albumtxt_y = 0;
1862 char *albumtxt;
1863 int c;
1864 /* Draw album text */
1865 if ( pf_state == pf_scrolling ) {
1866 c = ((slide_frame & 0xffff )/ 255);
1867 if (step < 0) c = 255-c;
1868 if (c > 128 ) { /* half way to next slide .. still not perfect! */
1869 albumtxt = get_album_name(center_index+step);
1870 c = (c-128)*2;
1872 else {
1873 albumtxt = get_album_name(center_index);
1874 c = (128-c)*2;
1877 else {
1878 c= 255;
1879 albumtxt = get_album_name(center_index);
1882 rb->lcd_set_foreground(LCD_RGBPACK(c,c,c));
1883 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1884 if (center_index != prev_center_index) {
1885 albumtxt_x = 0;
1886 albumtxt_dir = -1;
1887 prev_center_index = center_index;
1889 albumtxt_y = LCD_HEIGHT-albumtxt_h-10;
1891 if (albumtxt_w > LCD_WIDTH ) {
1892 rb->lcd_putsxy(albumtxt_x, albumtxt_y , albumtxt);
1893 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
1894 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
1895 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
1896 albumtxt_x += albumtxt_dir;
1899 else {
1900 rb->lcd_putsxy((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
1908 Main function that also contain the main plasma
1909 algorithm.
1911 int main(void)
1913 int ret;
1914 draw_splashscreen();
1916 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
1917 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
1918 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
1919 return PLUGIN_ERROR;
1923 if (!read_pfconfig()) {
1924 rb->splash(HZ, "Error in config. Please delete " CONFIG_FILE);
1925 return PLUGIN_ERROR;
1928 if (!allocate_buffers()) {
1929 rb->splash(HZ, "Could not allocate temporary buffers");
1930 return PLUGIN_ERROR;
1933 ret = create_album_index();
1934 if (ret == ERROR_BUFFER_FULL) {
1935 rb->splash(HZ, "Not enough memory for album names");
1936 return PLUGIN_ERROR;
1937 } else if (ret == ERROR_NO_ALBUMS) {
1938 rb->splash(HZ, "No albums found. Please enable database");
1939 return PLUGIN_ERROR;
1942 if (!create_albumart_cache(config.avg_album_width == 0)) {
1943 rb->splash(HZ, "Could not create album art cache");
1944 return PLUGIN_ERROR;
1947 if (!create_empty_slide(false)) {
1948 rb->splash(HZ, "Could not load the empty slide");
1949 return PLUGIN_ERROR;
1952 if (!free_buffers()) {
1953 rb->splash(HZ, "Could not free temporary buffers");
1954 return PLUGIN_ERROR;
1957 if (!create_pf_thread()) {
1958 rb->splash(HZ, "Cannot create thread!");
1959 return PLUGIN_ERROR;
1962 int i;
1964 /* initialize */
1965 int min_slide_cache = fmin(number_of_slides, SLIDE_CACHE_SIZE);
1966 for (i = 0; i < min_slide_cache; i++) {
1967 cache[i].hid = -1;
1968 cache[i].touched = 0;
1969 slide_cache_stack[i] = SLIDE_CACHE_SIZE-i-1;
1971 slide_cache_stack_index = min_slide_cache-1;
1972 slide_cache_in_use = 0;
1973 buffer = rb->lcd_framebuffer;
1975 pf_state = pf_idle;
1977 track_index = -1;
1978 extra_fade = 0;
1979 center_index = 0;
1980 slide_frame = 0;
1981 step = 0;
1982 target = 0;
1983 fade = 256;
1985 recalc_table();
1986 reset_slides();
1988 char fpstxt[10];
1989 int button;
1991 int frames = 0;
1992 long last_update = *rb->current_tick;
1993 long current_update;
1994 long update_interval = 100;
1995 int fps = 0;
1997 bool instant_update;
1998 old_drawmode = rb->lcd_get_drawmode();
1999 rb->lcd_set_drawmode(DRMODE_FG);
2000 while (true) {
2001 current_update = *rb->current_tick;
2002 frames++;
2004 /* Initial rendering */
2005 instant_update = false;
2007 /* Handle states */
2008 switch ( pf_state ) {
2009 case pf_scrolling:
2010 update_scroll_animation();
2011 render_all_slides();
2012 instant_update = true;
2013 break;
2014 case pf_cover_in:
2015 update_cover_in_animation();
2016 render_all_slides();
2017 instant_update = true;
2018 break;
2019 case pf_cover_out:
2020 update_cover_out_animation();
2021 render_all_slides();
2022 instant_update = true;
2023 break;
2024 case pf_show_tracks:
2025 show_track_list();
2026 break;
2027 case pf_idle:
2028 render_all_slides();
2029 break;
2032 /* Calculate FPS */
2033 if (current_update - last_update > update_interval) {
2034 fps = frames * HZ / (current_update - last_update);
2035 last_update = current_update;
2036 frames = 0;
2039 /* Draw FPS */
2040 if (config.show_fps) {
2041 rb->lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
2042 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2043 rb->lcd_putsxy(0, 0, fpstxt);
2046 draw_album_text();
2049 /* Copy offscreen buffer to LCD and give time to other threads */
2050 rb->lcd_update();
2051 rb->yield();
2053 /*/ Handle buttons */
2054 button = pluginlib_getaction(rb, instant_update ? 0 : HZ/16,
2055 plugin_contexts, NB_ACTION_CONTEXTS);
2057 switch (button) {
2058 case PICTUREFLOW_QUIT:
2059 return PLUGIN_OK;
2061 case PICTUREFLOW_MENU:
2062 if ( pf_state == pf_idle || pf_state == pf_scrolling ) {
2063 ret = main_menu();
2064 if ( ret == -1 ) return PLUGIN_OK;
2065 if ( ret != 0 ) return i;
2066 rb->lcd_set_drawmode(DRMODE_FG);
2068 else {
2069 pf_state = pf_cover_out;
2071 break;
2073 case PICTUREFLOW_NEXT_ALBUM:
2074 case PICTUREFLOW_NEXT_ALBUM_REPEAT:
2075 #ifdef SCROLLWHEEL
2076 if ( pf_state == pf_show_tracks )
2077 select_next_track();
2078 #endif
2079 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2080 show_next_slide();
2081 break;
2083 case PICTUREFLOW_PREV_ALBUM:
2084 case PICTUREFLOW_PREV_ALBUM_REPEAT:
2085 #ifdef SCROLLWHEEL
2086 if ( pf_state == pf_show_tracks )
2087 select_prev_track();
2088 #endif
2089 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2090 show_previous_slide();
2091 break;
2093 #ifndef SCROLLWHEEL
2094 case PICTUREFLOW_NEXT_TRACK:
2095 case PICTUREFLOW_NEXT_TRACK_REPEAT:
2096 if ( pf_state == pf_show_tracks )
2097 select_next_track();
2098 break;
2100 case PICTUREFLOW_PREV_TRACK:
2101 case PICTUREFLOW_PREV_TRACK_REPEAT:
2102 if ( pf_state == pf_show_tracks )
2103 select_prev_track();
2104 break;
2105 #endif
2107 case PICTUREFLOW_SELECT_ALBUM:
2108 if ( pf_state == pf_idle ) {
2109 reset_track_list();
2110 pf_state = pf_cover_in;
2112 if ( pf_state == pf_show_tracks )
2113 pf_state = pf_cover_out;
2114 break;
2116 default:
2117 if (rb->default_event_handler_ex(button, cleanup, NULL)
2118 == SYS_USB_CONNECTED)
2119 return PLUGIN_USB_CONNECTED;
2120 break;
2127 /*************************** Plugin entry point ****************************/
2129 enum plugin_status plugin_start(const struct plugin_api *api, const void *parameter)
2131 int ret;
2133 rb = api; /* copy to global api pointer */
2134 (void) parameter;
2135 #if LCD_DEPTH > 1
2136 rb->lcd_set_backdrop(NULL);
2137 #endif
2138 /* Turn off backlight timeout */
2139 backlight_force_on(rb); /* backlight control in lib/helper.c */
2140 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2141 rb->cpu_boost(true);
2142 #endif
2143 ret = main();
2144 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2145 rb->cpu_boost(false);
2146 #endif
2147 if ( ret == PLUGIN_OK ) {
2148 if (!write_pfconfig()) {
2149 rb->splash(HZ, "Error writing config.");
2150 ret = PLUGIN_ERROR;
2154 end_pf_thread();
2155 cleanup(NULL);
2156 return ret;