very minor code police. also fix a possible but unlikely missed cpu_boost(false)
[Rockbox.git] / apps / plugins / pictureflow.c
blob9c98fd6102db7b8414f71dabb3490f1e02f9355f
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 "pluginlib_actions.h"
28 #include "helper.h"
29 #include "bmp.h"
30 #include "picture.h"
31 #include "pictureflow_logo.h"
32 #include "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 struct config_data {
181 long avg_album_width;
182 int spacing_between_slides;
183 int extra_spacing_for_center_slide;
184 int show_slides;
185 int zoom;
188 /** below we allocate the memory we want to use **/
190 static fb_data *buffer; /* for now it always points to the lcd framebuffer */
191 static PFreal rays[BUFFER_WIDTH];
192 static struct slide_data center_slide;
193 static struct slide_data left_slides[MAX_SLIDES_COUNT];
194 static struct slide_data right_slides[MAX_SLIDES_COUNT];
195 static int slide_frame;
196 static int step;
197 static int target;
198 static int fade;
199 static int center_index; /* index of the slide that is in the center */
200 static int itilt;
201 static PFreal offsetX;
202 static PFreal offsetY;
203 static bool show_fps; /* show fps in the main screen */
204 static int number_of_slides;
206 static struct slide_cache cache[SLIDE_CACHE_SIZE];
207 static int slide_cache_in_use;
209 /* use long for aligning */
210 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
211 static int slide_cache_stack[SLIDE_CACHE_SIZE]; /* queue (as array) for scheduling load_surface */
212 static int slide_cache_stack_index;
213 struct mutex slide_cache_stack_lock;
215 static int empty_slide_hid;
217 struct thread_entry *thread_id;
218 struct event_queue thread_q;
220 static char tmp_path_name[MAX_PATH];
222 static long uniqbuf[UNIQBUF_SIZE];
223 static struct tagcache_search tcs;
225 static struct album_data album[MAX_ALBUMS];
226 static char album_names[MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH];
227 static int album_count;
229 static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
230 static struct track_data tracks[MAX_TRACKS];
231 static int track_count;
232 static int track_index;
233 static int selected_track;
234 static int selected_track_pulse;
236 static fb_data *input_bmp_buffer;
237 static fb_data *output_bmp_buffer;
238 static int input_hid;
239 static int output_hid;
240 static struct config_data config;
242 static int old_drawmode;
244 static bool thread_is_running;
246 static int cover_animation_keyframe;
247 static int extra_fade;
249 static int albumtxt_x = 0;
250 static int albumtxt_dir = -1;
251 static int prev_center_index = -1;
253 static int start_index_track_list = 0;
254 static int track_list_visible_entries = 0;
255 static int track_scroll_index = 0;
256 static int track_scroll_dir = 1;
259 Proposals for transitions:
261 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
262 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
264 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
266 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
268 TODO:
269 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
270 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
272 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
274 enum pf_states {
275 pf_idle = 0,
276 pf_scrolling,
277 pf_cover_in,
278 pf_show_tracks,
279 pf_cover_out
282 static int pf_state;
284 /** code */
286 bool create_bmp(struct bitmap* input_bmp, char *target_path, bool resize);
287 int load_surface(int);
289 static inline PFreal fmul(PFreal a, PFreal b)
291 return (a*b) >> PFREAL_SHIFT;
294 /* There are some precision issues when not using (long long) which in turn
295 takes very long to compute... I guess the best solution would be to optimize
296 the computations so it only requires a single long */
297 static inline PFreal fdiv(PFreal num, PFreal den)
299 long long p = (long long) (num) << (PFREAL_SHIFT * 2);
300 long long q = p / (long long) den;
301 long long r = q >> PFREAL_SHIFT;
303 return r;
306 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
307 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
308 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
311 #if 0
312 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
313 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
315 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
316 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
318 static inline PFreal fmul(PFreal a, PFreal b)
320 return (a*b) >> PFREAL_SHIFT;
323 static inline PFreal fdiv(PFreal n, PFreal m)
325 return (n<<(PFREAL_SHIFT))/m;
327 #endif
329 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
330 static const PFreal sin_tab[] = {
331 3, 103, 202, 300, 394, 485, 571, 652,
332 726, 793, 853, 904, 947, 980, 1004, 1019,
333 1023, 1018, 1003, 978, 944, 901, 849, 789,
334 721, 647, 566, 479, 388, 294, 196, 97,
335 -4, -104, -203, -301, -395, -486, -572, -653,
336 -727, -794, -854, -905, -948, -981, -1005, -1020,
337 -1024, -1019, -1004, -979, -945, -902, -850, -790,
338 -722, -648, -567, -480, -389, -295, -197, -98,
342 static inline PFreal fsin(int iangle)
344 while(iangle < 0)
345 iangle += IANGLE_MAX;
346 iangle &= IANGLE_MASK;
348 int i = (iangle >> 4);
349 PFreal p = sin_tab[i];
350 PFreal q = sin_tab[(i+1)];
351 PFreal g = (q - p);
352 return p + g * (iangle-i*16)/16;
355 static inline PFreal fcos(int iangle)
357 return fsin(iangle + (IANGLE_MAX >> 2));
361 Create an index of all albums from the database.
362 Also store the album names so we can access them later.
364 int create_album_index(void)
366 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
367 album_count = 0;
368 rb->tagcache_search(&tcs, tag_album);
369 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
370 int l, old_l = 0;
371 album[0].name_idx = 0;
372 while (rb->tagcache_get_next(&tcs) && album_count < MAX_ALBUMS)
374 l = rb->strlen(tcs.result) + 1;
375 if ( album_count > 0 )
376 album[album_count].name_idx = album[album_count-1].name_idx + old_l;
378 if ( (album[album_count].name_idx + l) > MAX_ALBUMS*AVG_ALBUM_NAME_LENGTH )
379 /* not enough memory */
380 return ERROR_BUFFER_FULL;
382 rb->strcpy(album_names + album[album_count].name_idx, tcs.result);
383 album[album_count].seek = tcs.result_seek;
384 old_l = l;
385 album_count++;
387 rb->tagcache_search_finish(&tcs);
389 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
393 Return a pointer to the album name of the given slide_index
395 char* get_album_name(const int slide_index)
397 return album_names + album[slide_index].name_idx;
401 Return a pointer to the track name of the active album
402 create_track_index has to be called first.
404 char* get_track_name(const int track_index)
406 if ( track_index < track_count )
407 return track_names + tracks[track_index].name_idx;
408 return 0;
412 Create the track index of the given slide_index.
414 int create_track_index(const int slide_index)
416 if ( slide_index == track_index ) {
417 return -1;
420 if (!rb->tagcache_search(&tcs, tag_title))
421 return -1;
423 int ret = 0;
424 char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4];
425 int temp_seeks[MAX_TRACKS];
427 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
428 track_count=0;
429 int string_index = 0;
430 int l, track_num, heighest_index = 0;
432 for(l=0;l<MAX_TRACKS;l++)
433 temp_titles[l][0] = '\0';
434 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
436 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
437 if (track_num >= 0)
439 rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]), "%d: %s",
440 track_num+1, tcs.result);
441 temp_seeks[track_num] = tcs.result_seek;
443 else
445 track_num = 0;
446 while (temp_titles[track_num][0] != '\0')
447 track_num++;
448 rb->strcpy(temp_titles[track_num], tcs.result);
449 temp_seeks[track_num] = tcs.result_seek;
451 if (track_num > heighest_index)
452 heighest_index = track_num;
453 track_count++;
456 rb->tagcache_search_finish(&tcs);
457 track_index = slide_index;
459 /* now fix the track list order */
460 l = 0;
461 track_count = 0;
462 while (l < heighest_index &&
463 string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH)
465 if (temp_titles[l][0] != '\0')
467 rb->strcpy(track_names + string_index, temp_titles[l]);
468 tracks[track_count].name_idx = string_index;
469 tracks[track_count].seek = temp_seeks[l];
470 string_index += rb->strlen(temp_titles[l]) + 1;
471 track_count++;
473 l++;
475 if (ret != 0)
476 return ret;
477 else
478 return (track_count > 0) ? 0 : -1;
483 Determine filename of the album art for the given slide_index and
484 store the result in buf.
485 The algorithm looks for the first track of the given album uses
486 find_albumart to find the filename.
488 bool get_albumart_for_index_from_db(const int slide_index, char *buf, int buflen)
490 if ( slide_index == -1 )
492 rb->strncpy( buf, EMPTY_SLIDE, buflen );
495 if (!rb->tagcache_search(&tcs, tag_filename))
496 return false;
498 bool result;
499 /* find the first track of the album */
500 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
502 if ( rb->tagcache_get_next(&tcs) ) {
503 struct mp3entry id3;
504 char size[9];
505 rb->snprintf(size, sizeof(size), ".%dx%d", PREFERRED_IMG_WIDTH,
506 PREFERRED_IMG_HEIGHT);
507 rb->strncpy( (char*)&id3.path, tcs.result, MAX_PATH );
508 id3.album = get_album_name(slide_index);
509 if ( rb->search_albumart_files(&id3, size, buf, buflen) )
510 result = true;
511 else if ( rb->search_albumart_files(&id3, "", buf, buflen) )
512 result = true;
513 else
514 result = false;
516 else {
517 /* did not find a matching track */
518 result = false;
520 rb->tagcache_search_finish(&tcs);
521 return result;
525 Draw the PictureFlow logo
527 void draw_splashscreen(void)
529 struct screen* display = rb->screens[0];
531 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
532 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
533 rb->lcd_clear_display();
535 const struct picture* logo = &(logos[display->screen_type]);
536 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
538 rb->lcd_update();
543 Draw a simple progress bar
545 void draw_progressbar(int step)
547 int txt_w, txt_h;
548 const int bar_height = 22;
549 const int w = LCD_WIDTH - 20;
550 const int x = 10;
552 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
554 int y = (LCD_HEIGHT - txt_h)/2;
556 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
557 y += (txt_h + 5);
559 rb->lcd_set_foreground(LCD_RGBPACK(100,100,100));
560 rb->lcd_drawrect(x, y, w+2, bar_height);
561 rb->lcd_set_foreground(LCD_RGBPACK(165, 231, 82));
563 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
564 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
565 rb->lcd_update();
566 rb->yield();
570 Allocate temporary buffers
572 bool allocate_buffers(void)
574 int input_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data );
575 int output_size = MAX_IMG_WIDTH * MAX_IMG_HEIGHT * sizeof( fb_data ) * 2;
577 input_hid = rb->bufalloc(NULL, input_size, TYPE_BITMAP);
579 if (input_hid < 0)
580 return false;
582 if (rb->bufgetdata(input_hid, 0, (void *)&input_bmp_buffer) < input_size) {
583 rb->bufclose(input_hid);
584 return false;
587 output_hid = rb->bufalloc(NULL, output_size, TYPE_BITMAP);
589 if (output_hid < 0) {
590 rb->bufclose(input_hid);
591 return false;
594 if (rb->bufgetdata(output_hid, 0, (void *)&output_bmp_buffer) < output_size) {
595 rb->bufclose(output_hid);
596 return false;
598 return true;
603 Free the temporary buffers
605 bool free_buffers(void)
607 rb->bufclose(input_hid);
608 rb->bufclose(output_hid);
609 return true;
613 Precomupte the album art images and store them in CACHE_PREFIX.
615 bool create_albumart_cache(bool force)
617 number_of_slides = album_count;
618 int fh,ret;
620 if ( ! force && rb->file_exists( CACHE_PREFIX "/ready" ) ) return true;
622 int i, slides = 0;
623 struct bitmap input_bmp;
625 config.avg_album_width = 0;
626 for (i=0; i < album_count; i++)
628 draw_progressbar(i);
629 if (!get_albumart_for_index_from_db(i, tmp_path_name, MAX_PATH))
630 continue;
632 input_bmp.data = (char *)input_bmp_buffer;
633 ret = rb->read_bmp_file(tmp_path_name, &input_bmp,
634 sizeof(fb_data)*MAX_IMG_WIDTH*MAX_IMG_HEIGHT,
635 FORMAT_NATIVE);
636 if (ret <= 0) {
637 rb->splash(HZ, "Could not read bmp");
638 continue; /* skip missing/broken files */
642 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw", i);
643 if (!create_bmp(&input_bmp, tmp_path_name, false)) {
644 rb->splash(HZ, "Could not write bmp");
646 config.avg_album_width += input_bmp.width;
647 slides++;
648 if ( rb->button_get(false) == PICTUREFLOW_MENU ) return false;
650 if ( slides == 0 ) {
651 rb->splash(2*HZ, "No albums found");
652 return false;
654 config.avg_album_width /= slides;
655 if ( config.avg_album_width == 0 ) {
656 rb->splash(HZ, "album size is 0");
657 return false;
659 fh = rb->creat( CACHE_PREFIX "/ready" );
660 rb->close(fh);
661 return true;
665 Return the index on the stack of slide_index.
666 Return -1 if slide_index is not on the stack.
668 static inline int slide_stack_get_index(const int slide_index)
670 int i = slide_cache_stack_index + 1;
671 while (i--) {
672 if ( slide_cache_stack[i] == slide_index ) return i;
674 return -1;
678 Push the slide_index on the stack so the image will be loaded.
679 The algorithm tries to keep the center_index on top and the
680 slide_index as high as possible (so second if center_index is
681 on the stack).
683 void slide_stack_push(const int slide_index)
685 rb->mutex_lock(&slide_cache_stack_lock);
687 if ( slide_cache_stack_index == -1 ) {
688 /* empty stack, no checks at all */
689 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
690 rb->mutex_unlock(&slide_cache_stack_lock);
691 return;
694 int i = slide_stack_get_index( slide_index );
696 if ( i == slide_cache_stack_index ) {
697 /* slide_index is on top, so we do not change anything */
698 rb->mutex_unlock(&slide_cache_stack_lock);
699 return;
702 if ( i >= 0 ) {
703 /* slide_index is already on the stack, but not on top */
704 int tmp = slide_cache_stack[ slide_cache_stack_index ];
705 if ( tmp == center_index ) {
706 /* the center_index is on top of the stack so do not touch that */
707 if ( slide_cache_stack_index > 0 ) {
708 /* but maybe it is possible to swap the given slide_index to the second place */
709 tmp = slide_cache_stack[ slide_cache_stack_index -1 ];
710 slide_cache_stack[ slide_cache_stack_index - 1 ] = slide_cache_stack[ i ];
711 slide_cache_stack[ i ] = tmp;
714 else {
715 /* if the center_index is not on top (i.e. already loaded) bring the slide_index to the top */
716 slide_cache_stack[ slide_cache_stack_index ] = slide_cache_stack[ i ];
717 slide_cache_stack[ i ] = tmp;
720 else {
721 /* slide_index is not on the stack */
722 if ( slide_cache_stack_index >= SLIDE_CACHE_SIZE-1 ) {
723 /* if we exceeded the stack size, clear the first half of the stack */
724 slide_cache_stack_index = SLIDE_CACHE_SIZE/2;
725 for (i = 0; i <= slide_cache_stack_index ; i++)
726 slide_cache_stack[ i ] = slide_cache_stack[ i + slide_cache_stack_index ];
728 if ( slide_cache_stack[ slide_cache_stack_index ] == center_index ) {
729 /* if the center_index is on top leave it there */
730 slide_cache_stack[ slide_cache_stack_index ] = slide_index;
731 slide_cache_stack[ ++slide_cache_stack_index ] = center_index;
733 else {
734 /* usual stack case: push the slide_index on top */
735 slide_cache_stack[ ++slide_cache_stack_index ] = slide_index;
738 rb->mutex_unlock(&slide_cache_stack_lock);
743 Pop the topmost item from the stack and decrease the stack size
745 static inline int slide_stack_pop(void)
747 rb->mutex_lock(&slide_cache_stack_lock);
748 int result;
749 if ( slide_cache_stack_index >= 0 )
750 result = slide_cache_stack[ slide_cache_stack_index-- ];
751 else
752 result = -1;
753 rb->mutex_unlock(&slide_cache_stack_lock);
754 return result;
759 Load the slide into the cache.
760 Thus we have to queue the loading request in our thread while discarding the
761 oldest slide.
763 static inline void request_surface(const int slide_index)
765 slide_stack_push(slide_index);
766 rb->queue_post(&thread_q, EV_WAKEUP, 0);
771 Thread used for loading and preparing bitmaps in the background
773 void thread(void)
775 long sleep_time = 5 * HZ;
776 struct queue_event ev;
777 while (1) {
778 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
779 switch (ev.id) {
780 case EV_EXIT:
781 return;
782 case EV_WAKEUP:
783 /* we just woke up */
784 break;
786 int slide_index;
787 while ( (slide_index = slide_stack_pop()) != -1 ) {
788 load_surface( slide_index );
789 rb->queue_wait_w_tmo(&thread_q, &ev, HZ/10);
790 switch (ev.id) {
791 case EV_EXIT:
792 return;
800 End the thread by posting the EV_EXIT event
802 void end_pf_thread(void)
804 if ( thread_is_running ) {
805 rb->queue_post(&thread_q, EV_EXIT, 0);
806 rb->thread_wait(thread_id);
807 /* remove the thread's queue from the broadcast list */
808 rb->queue_delete(&thread_q);
809 thread_is_running = false;
816 Create the thread an setup the event queue
818 bool create_pf_thread(void)
820 rb->queue_init(&thread_q, true); /* put the thread's queue in the bcast list */
821 if ((thread_id = rb->create_thread(
822 thread,
823 thread_stack,
824 sizeof(thread_stack),
826 "Picture load thread"
827 IF_PRIO(, PRIORITY_BACKGROUND)
828 IF_COP(, CPU)
830 ) == NULL) {
831 return false;
833 thread_is_running = true;
834 return true;
838 Safe the given bitmap as filename in the pfraw format
840 bool save_pfraw(char* filename, struct bitmap *bm)
842 struct pfraw_header bmph;
843 bmph.width = bm->width;
844 bmph.height = bm->height;
845 int fh = rb->creat( filename );
846 if( fh < 0 ) return false;
847 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
848 int y;
849 for( y = 0; y < bm->height; y++ )
851 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
852 rb->write( fh, d, sizeof( fb_data ) * bm->width );
854 rb->close( fh );
855 return true;
860 Read the pfraw image given as filename and return the hid of the buffer
862 int read_pfraw(char* filename)
864 struct pfraw_header bmph;
865 int fh = rb->open(filename, O_RDONLY);
866 rb->read(fh, &bmph, sizeof(struct pfraw_header));
867 if( fh < 0 ) {
868 return empty_slide_hid;
871 int size = sizeof(struct bitmap) + sizeof( fb_data ) * bmph.width * bmph.height;
873 int hid = rb->bufalloc(NULL, size, TYPE_BITMAP);
874 if (hid < 0) {
875 rb->close( fh );
876 return -1;
879 struct bitmap *bm;
880 if (rb->bufgetdata(hid, 0, (void *)&bm) < size) {
881 rb->close( fh );
882 return -1;
885 bm->width = bmph.width;
886 bm->height = bmph.height;
887 bm->format = FORMAT_NATIVE;
888 bm->data = ((unsigned char *)bm + sizeof(struct bitmap));
890 int y;
891 for( y = 0; y < bm->height; y++ )
893 fb_data *d = (fb_data*)( bm->data ) + (y*bm->width);
894 rb->read( fh, d , sizeof( fb_data ) * bm->width );
896 rb->close( fh );
897 return hid;
902 Create the slide with its reflection for the given slide_index and filename
903 and store it as pfraw in CACHE_PREFIX/[slide_index].pfraw
905 bool create_bmp(struct bitmap *input_bmp, char *target_path, bool resize)
907 struct bitmap output_bmp;
909 output_bmp.format = input_bmp->format;
910 output_bmp.data = (char *)output_bmp_buffer;
912 if ( resize ) {
913 /* resize image */
914 output_bmp.width = config.avg_album_width;
915 output_bmp.height = config.avg_album_width;
916 simple_resize_bitmap(input_bmp, &output_bmp);
918 /* Resized bitmap is now in the output buffer,
919 copy it back to the input buffer */
920 rb->memcpy(input_bmp_buffer, output_bmp_buffer,
921 config.avg_album_width * config.avg_album_width * sizeof(fb_data));
922 input_bmp->data = (char *)input_bmp_buffer;
923 input_bmp->width = output_bmp.width;
924 input_bmp->height = output_bmp.height;
927 output_bmp.width = input_bmp->width * 2;
928 output_bmp.height = input_bmp->height;
930 fb_data *src = (fb_data *)input_bmp->data;
931 fb_data *dst = (fb_data *)output_bmp.data;
933 /* transpose the image, this is to speed-up the rendering
934 because we process one column at a time
935 (and much better and faster to work row-wise, i.e in one scanline) */
936 int hofs = input_bmp->width / 3;
937 rb->memset(dst, 0, sizeof(fb_data) * output_bmp.width * output_bmp.height);
938 int x, y;
939 for (x = 0; x < input_bmp->width; x++)
940 for (y = 0; y < input_bmp->height; y++)
941 dst[output_bmp.width * x + (hofs + y)] =
942 src[y * input_bmp->width + x];
944 /* create the reflection */
945 int ht = input_bmp->height - hofs;
946 int hte = ht;
947 for (x = 0; x < input_bmp->width; x++) {
948 for (y = 0; y < ht; y++) {
949 fb_data color = src[x + input_bmp->width * (input_bmp->height - y - 1)];
950 int r = RGB_UNPACK_RED(color) * (hte - y) / hte * 3 / 5;
951 int g = RGB_UNPACK_GREEN(color) * (hte - y) / hte * 3 / 5;
952 int b = RGB_UNPACK_BLUE(color) * (hte - y) / hte * 3 / 5;
953 dst[output_bmp.height + hofs + y + output_bmp.width * x] =
954 LCD_RGBPACK(r, g, b);
957 return save_pfraw(target_path, &output_bmp);
962 Load the surface for the given slide_index into the cache at cache_index.
964 static inline bool load_and_prepare_surface(const int slide_index,
965 const int cache_index)
967 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
968 slide_index);
970 int hid = read_pfraw(tmp_path_name);
971 if (hid < 0)
972 return false;
974 cache[cache_index].hid = hid;
976 if ( cache_index < SLIDE_CACHE_SIZE ) {
977 cache[cache_index].index = slide_index;
978 cache[cache_index].touched = *rb->current_tick;
981 return true;
986 Load the surface from a bmp and overwrite the oldest slide in the cache
987 if necessary.
989 int load_surface(const int slide_index)
991 long oldest_tick = *rb->current_tick;
992 int oldest_slide = 0;
993 int i;
994 if ( slide_cache_in_use < SLIDE_CACHE_SIZE ) { /* initial fill */
995 oldest_slide = slide_cache_in_use;
996 load_and_prepare_surface(slide_index, slide_cache_in_use++);
998 else {
999 for (i = 0; i < SLIDE_CACHE_SIZE; i++) { /* look for oldest slide */
1000 if (cache[i].touched < oldest_tick) {
1001 oldest_slide = i;
1002 oldest_tick = cache[i].touched;
1005 if (cache[oldest_slide].hid != empty_slide_hid) {
1006 rb->bufclose(cache[oldest_slide].hid);
1007 cache[oldest_slide].hid = -1;
1009 load_and_prepare_surface(slide_index, oldest_slide);
1011 return oldest_slide;
1016 Get a slide from the buffer
1018 static inline struct bitmap *get_slide(const int hid)
1020 if (hid < 0)
1021 return NULL;
1023 struct bitmap *bmp;
1025 ssize_t ret = rb->bufgetdata(hid, 0, (void *)&bmp);
1026 if (ret < 0)
1027 return NULL;
1029 return bmp;
1034 Return the requested surface
1036 static inline struct bitmap *surface(const int slide_index)
1038 if (slide_index < 0)
1039 return 0;
1040 if (slide_index >= number_of_slides)
1041 return 0;
1043 int i;
1044 for (i = 0; i < slide_cache_in_use; i++) { /* maybe do the inverse mapping => implies dynamic allocation? */
1045 if ( cache[i].index == slide_index ) {
1046 /* We have already loaded our slide, so touch it and return it. */
1047 cache[i].touched = *rb->current_tick;
1048 return get_slide(cache[i].hid);
1051 request_surface(slide_index);
1052 return get_slide(empty_slide_hid);
1056 adjust slides so that they are in "steady state" position
1058 void reset_slides(void)
1060 center_slide.angle = 0;
1061 center_slide.cx = 0;
1062 center_slide.cy = 0;
1063 center_slide.distance = 0;
1064 center_slide.slide_index = center_index;
1066 int i;
1067 for (i = 0; i < config.show_slides; i++) {
1068 struct slide_data *si = &left_slides[i];
1069 si->angle = itilt;
1070 si->cx = -(offsetX + config.spacing_between_slides * i * PFREAL_ONE);
1071 si->cy = offsetY;
1072 si->slide_index = center_index - 1 - i;
1073 si->distance = 0;
1076 for (i = 0; i < config.show_slides; i++) {
1077 struct slide_data *si = &right_slides[i];
1078 si->angle = -itilt;
1079 si->cx = offsetX + config.spacing_between_slides * i * PFREAL_ONE;
1080 si->cy = offsetY;
1081 si->slide_index = center_index + 1 + i;
1082 si->distance = 0;
1088 Updates look-up table and other stuff necessary for the rendering.
1089 Call this when the viewport size or slide dimension is changed.
1091 void recalc_table(void)
1093 int w = (BUFFER_WIDTH + 1) / 2;
1094 int h = (BUFFER_HEIGHT + 1) / 2;
1095 int i;
1096 for (i = 0; i < w; i++) {
1097 PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2 * h);
1098 rays[w - i - 1] = -gg;
1099 rays[w + i] = gg;
1102 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1104 offsetX = config.avg_album_width / 2 * (PFREAL_ONE - fcos(itilt));
1105 offsetY = config.avg_album_width / 2 * fsin(itilt);
1106 offsetX += config.avg_album_width * PFREAL_ONE;
1107 offsetY += config.avg_album_width * PFREAL_ONE / 4;
1108 offsetX += config.extra_spacing_for_center_slide << PFREAL_SHIFT;
1113 Fade the given color by spreading the fb_data (ushort)
1114 to an uint, multiply and compress the result back to a ushort.
1116 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1117 static inline fb_data fade_color(fb_data c, unsigned int a)
1119 c = swap16(c);
1120 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1121 return swap16( (fb_data) (p | ( p >> 16 )) );
1123 #else
1124 static inline fb_data fade_color(fb_data c, unsigned int a)
1126 unsigned int p = ((((c|(c<<16)) & 0x07e0f81f) * a) >> 5) & 0x07e0f81f;
1127 return (p | ( p >> 16 ));
1129 #endif
1135 Render a single slide
1137 void render_slide(struct slide_data *slide, struct rect *result_rect,
1138 const int alpha, int col1, int col2)
1140 rb->memset(result_rect, 0, sizeof(struct rect));
1141 struct bitmap *bmp = surface(slide->slide_index);
1142 if (!bmp) {
1143 return;
1145 fb_data *src = (fb_data *)bmp->data;
1147 const int sw = bmp->height;
1148 const int sh = bmp->width;
1150 const int h = LCD_HEIGHT;
1151 const int w = LCD_WIDTH;
1153 if (col1 > col2) {
1154 int c = col2;
1155 col2 = col1;
1156 col1 = c;
1159 col1 = (col1 >= 0) ? col1 : 0;
1160 col2 = (col2 >= 0) ? col2 : w - 1;
1161 col1 = fmin(col1, w - 1);
1162 col2 = fmin(col2, w - 1);
1164 int distance = (h + slide->distance) * 100 / config.zoom;
1165 if (distance < 100 ) distance = 100; /* clamp distances */
1166 PFreal sdx = fcos(slide->angle);
1167 PFreal sdy = fsin(slide->angle);
1168 PFreal xs = slide->cx - bmp->width * sdx / 4;
1169 PFreal ys = slide->cy - bmp->width * sdy / 4;
1170 PFreal dist = distance * PFREAL_ONE;
1172 const int alpha4 = alpha >> 3;
1174 int xi = fmax((PFreal) 0,
1175 ((w * PFREAL_ONE / 2) +
1176 fdiv(xs * h, dist + ys)) >> PFREAL_SHIFT);
1177 if (xi >= w) {
1178 return;
1181 bool flag = false;
1182 result_rect->left = xi;
1183 int x;
1184 for (x = fmax(xi, col1); x <= col2; x++) {
1185 PFreal hity = 0;
1186 PFreal fk = rays[x];
1187 if (sdy) {
1188 fk = fk - fdiv(sdx, sdy);
1189 hity = -fdiv(( rays[x] * distance
1190 - slide->cx
1191 + slide->cy * sdx / sdy), fk);
1194 dist = distance * PFREAL_ONE + hity;
1195 if (dist < 0)
1196 continue;
1198 PFreal hitx = fmul(dist, rays[x]);
1200 PFreal hitdist = fdiv(hitx - slide->cx, sdx);
1202 const int column = (sw >> 1) + (hitdist >> PFREAL_SHIFT);
1203 if (column >= sw)
1204 break;
1206 if (column < 0)
1207 continue;
1209 result_rect->right = x;
1210 if (!flag)
1211 result_rect->left = x;
1212 flag = true;
1214 int y1 = (h >> 1);
1215 int y2 = y1 + 1;
1216 fb_data *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
1217 fb_data *pixel2 = &buffer[y2 * BUFFER_WIDTH + x];
1218 const int pixelstep = pixel2 - pixel1;
1220 int center = (sh >> 1);
1221 int dy = dist / h;
1222 int p1 = center * PFREAL_ONE - (dy >> 2);
1223 int p2 = center * PFREAL_ONE + (dy >> 2);
1225 const fb_data *ptr = &src[column * bmp->width];
1227 if (alpha == 256)
1228 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1229 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
1230 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
1231 p1 -= dy;
1232 p2 += dy;
1233 y1--;
1234 y2++;
1235 pixel1 -= pixelstep;
1236 pixel2 += pixelstep;
1237 } else
1238 while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
1239 *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT], alpha4);
1240 *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT], alpha4);
1241 p1 -= dy;
1242 p2 += dy;
1243 y1--;
1244 y2++;
1245 pixel1 -= pixelstep;
1246 pixel2 += pixelstep;
1249 /* let the music play... */
1250 rb->yield();
1252 result_rect->top = 0;
1253 result_rect->bottom = h - 1;
1254 return;
1259 Jump the the given slide_index
1261 static inline void set_current_slide(const int slide_index)
1263 step = 0;
1264 center_index = fbound(slide_index, 0, number_of_slides - 1);
1265 target = center_index;
1266 slide_frame = slide_index << 16;
1267 reset_slides();
1271 Start the animation for changing slides
1273 void start_animation(void)
1275 step = (target < center_slide.slide_index) ? -1 : 1;
1276 pf_state = pf_scrolling;
1280 Go to the previous slide
1282 void show_previous_slide(void)
1284 if (step == 0) {
1285 if (center_index > 0) {
1286 target = center_index - 1;
1287 start_animation();
1289 } else if ( step > 0 ) {
1290 target = center_index;
1291 start_animation();
1292 } else {
1293 target = fmax(0, center_index - 2);
1299 Go to the next slide
1301 void show_next_slide(void)
1303 if (step == 0) {
1304 if (center_index < number_of_slides - 1) {
1305 target = center_index + 1;
1306 start_animation();
1308 } else if ( step < 0 ) {
1309 target = center_index;
1310 start_animation();
1311 } else {
1312 target = fmin(center_index + 2, number_of_slides - 1);
1318 Return true if the rect has size 0
1320 static inline bool is_empty_rect(struct rect *r)
1322 return ((r->left == 0) && (r->right == 0) && (r->top == 0)
1323 && (r->bottom == 0));
1328 Render the slides. Updates only the offscreen buffer.
1330 void render_all_slides(void)
1332 rb->lcd_set_background(LCD_RGBPACK(0,0,0));
1333 rb->lcd_clear_display(); /* TODO: Optimizes this by e.g. invalidating rects */
1335 int nleft = config.show_slides;
1336 int nright = config.show_slides;
1338 struct rect r;
1339 r.left = LCD_WIDTH; r.top = 0; r.bottom = 0; r.right = 0;
1340 render_slide(&center_slide, &r, 256, -1, -1);
1341 #ifdef DEBUG_DRAW
1342 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1343 #endif
1344 int c1 = r.left;
1345 int c2 = r.right;
1346 int index;
1347 if (step == 0) {
1348 /* no animation, boring plain rendering */
1349 for (index = 0; index < nleft - 1; index++) {
1350 int alpha = (index < nleft - 2) ? 256 : 128;
1351 alpha -= extra_fade;
1352 if (alpha < 0 ) alpha = 0;
1353 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1354 if (!is_empty_rect(&r)) {
1355 #ifdef DEBUG_DRAW
1356 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1357 #endif
1358 c1 = r.left;
1361 for (index = 0; index < nright - 1; index++) {
1362 int alpha = (index < nright - 2) ? 256 : 128;
1363 alpha -= extra_fade;
1364 if (alpha < 0 ) alpha = 0;
1365 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1366 BUFFER_WIDTH);
1367 if (!is_empty_rect(&r)) {
1368 #ifdef DEBUG_DRAW
1369 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1370 #endif
1371 c2 = r.right;
1374 } else {
1375 if ( step < 0 ) c1 = BUFFER_WIDTH;
1376 /* the first and last slide must fade in/fade out */
1377 for (index = 0; index < nleft; index++) {
1378 int alpha = 256;
1379 if (index == nleft - 1)
1380 alpha = (step > 0) ? 0 : 128 - fade / 2;
1381 if (index == nleft - 2)
1382 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1383 if (index == nleft - 3)
1384 alpha = (step > 0) ? 256 - fade / 2 : 256;
1385 render_slide(&left_slides[index], &r, alpha, 0, c1 - 1);
1387 if (!is_empty_rect(&r)) {
1388 #ifdef DEBUG_DRAW
1389 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1390 #endif
1391 c1 = r.left;
1394 if ( step > 0 ) c2 = 0;
1395 for (index = 0; index < nright; index++) {
1396 int alpha = (index < nright - 2) ? 256 : 128;
1397 if (index == nright - 1)
1398 alpha = (step > 0) ? fade / 2 : 0;
1399 if (index == nright - 2)
1400 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1401 if (index == nright - 3)
1402 alpha = (step > 0) ? 256 : 128 + fade / 2;
1403 render_slide(&right_slides[index], &r, alpha, c2 + 1,
1404 BUFFER_WIDTH);
1405 if (!is_empty_rect(&r)) {
1406 #ifdef DEBUG_DRAW
1407 rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top);
1408 #endif
1409 c2 = r.right;
1417 Updates the animation effect. Call this periodically from a timer.
1419 void update_scroll_animation(void)
1421 if (step == 0)
1422 return;
1424 int speed = 16384;
1425 int i;
1427 /* deaccelerate when approaching the target */
1428 if (true) {
1429 const int max = 2 * 65536;
1431 int fi = slide_frame;
1432 fi -= (target << 16);
1433 if (fi < 0)
1434 fi = -fi;
1435 fi = fmin(fi, max);
1437 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1438 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1441 slide_frame += speed * step;
1443 int index = slide_frame >> 16;
1444 int pos = slide_frame & 0xffff;
1445 int neg = 65536 - pos;
1446 int tick = (step < 0) ? neg : pos;
1447 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1449 /* the leftmost and rightmost slide must fade away */
1450 fade = pos / 256;
1452 if (step < 0)
1453 index++;
1454 if (center_index != index) {
1455 center_index = index;
1456 slide_frame = index << 16;
1457 center_slide.slide_index = center_index;
1458 for (i = 0; i < config.show_slides; i++)
1459 left_slides[i].slide_index = center_index - 1 - i;
1460 for (i = 0; i < config.show_slides; i++)
1461 right_slides[i].slide_index = center_index + 1 + i;
1464 center_slide.angle = (step * tick * itilt) >> 16;
1465 center_slide.cx = -step * fmul(offsetX, ftick);
1466 center_slide.cy = fmul(offsetY, ftick);
1468 if (center_index == target) {
1469 reset_slides();
1470 pf_state = pf_idle;
1471 step = 0;
1472 fade = 256;
1473 return;
1476 for (i = 0; i < config.show_slides; i++) {
1477 struct slide_data *si = &left_slides[i];
1478 si->angle = itilt;
1479 si->cx =
1480 -(offsetX + config.spacing_between_slides * i * PFREAL_ONE + step
1481 * config.spacing_between_slides * ftick);
1482 si->cy = offsetY;
1485 for (i = 0; i < config.show_slides; i++) {
1486 struct slide_data *si = &right_slides[i];
1487 si->angle = -itilt;
1488 si->cx =
1489 offsetX + config.spacing_between_slides * i * PFREAL_ONE - step
1490 * config.spacing_between_slides * ftick;
1491 si->cy = offsetY;
1494 if (step > 0) {
1495 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1496 right_slides[0].angle = -(neg * itilt) >> 16;
1497 right_slides[0].cx = fmul(offsetX, ftick);
1498 right_slides[0].cy = fmul(offsetY, ftick);
1499 } else {
1500 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1501 left_slides[0].angle = (pos * itilt) >> 16;
1502 left_slides[0].cx = -fmul(offsetX, ftick);
1503 left_slides[0].cy = fmul(offsetY, ftick);
1506 /* must change direction ? */
1507 if (target < index)
1508 if (step > 0)
1509 step = -1;
1510 if (target > index)
1511 if (step < 0)
1512 step = 1;
1517 Cleanup the plugin
1519 void cleanup(void *parameter)
1521 (void) parameter;
1522 /* Turn on backlight timeout (revert to settings) */
1523 backlight_use_settings(rb); /* backlight control in lib/helper.c */
1525 int i;
1526 for (i = 0; i < slide_cache_in_use; i++) {
1527 rb->bufclose(cache[i].hid);
1529 if ( empty_slide_hid != - 1)
1530 rb->bufclose(empty_slide_hid);
1531 rb->lcd_set_drawmode(old_drawmode);
1535 Create the "?" slide, that is shown while loading
1536 or when no cover was found.
1538 int create_empty_slide(bool force)
1540 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1541 struct bitmap input_bmp;
1542 input_bmp.width = BMPWIDTH_pictureflow_emptyslide;
1543 input_bmp.height = BMPHEIGHT_pictureflow_emptyslide;
1544 input_bmp.format = FORMAT_NATIVE;
1545 input_bmp.data = (char*) &pictureflow_emptyslide;
1546 if ( ! create_bmp(&input_bmp, EMPTY_SLIDE, true) ) return false;
1549 empty_slide_hid = read_pfraw( EMPTY_SLIDE );
1550 if (empty_slide_hid == -1 ) return false;
1552 return true;
1557 Shows the settings menu
1559 int settings_menu(void) {
1560 int selection = 0;
1562 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1563 "Spacing", "Center margin", "Number of slides", "Zoom",
1564 "Rebuild cache");
1566 do {
1567 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1568 switch(selection) {
1569 case 0:
1570 rb->set_bool("Show FPS", &show_fps);
1571 break;
1573 case 1:
1574 rb->set_int("Spacing between slides", "", 1,
1575 &(config.spacing_between_slides),
1576 NULL, 1, 0, 100, NULL );
1577 recalc_table();
1578 reset_slides();
1579 break;
1581 case 2:
1582 rb->set_int("Center margin", "", 1,
1583 &(config.extra_spacing_for_center_slide),
1584 NULL, 1, -50, 50, NULL );
1585 recalc_table();
1586 reset_slides();
1587 break;
1589 case 3:
1590 rb->set_int("Number of slides", "", 1, &(config.show_slides),
1591 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1592 recalc_table();
1593 reset_slides();
1594 break;
1596 case 4:
1597 rb->set_int("Number of slides", "", 1, &(config.zoom),
1598 NULL, 1, 10, 300, NULL );
1599 recalc_table();
1600 reset_slides();
1601 break;
1603 case 5:
1604 rb->remove(CACHE_PREFIX "/ready");
1605 rb->remove(EMPTY_SLIDE);
1606 rb->splash(HZ, "Cache will be rebuilt on next restart");
1607 break;
1609 case MENU_ATTACHED_USB:
1610 return PLUGIN_USB_CONNECTED;
1612 } while ( selection >= 0 );
1613 return 0;
1617 Show the main menu
1619 int main_menu(void)
1621 int selection = 0;
1622 int result;
1624 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1626 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
1627 "Settings", "Return", "Quit");
1629 while (1) {
1630 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
1631 case 0:
1632 result = settings_menu();
1633 if ( result != 0 ) return result;
1634 break;
1636 case 1:
1637 return 0;
1639 case 2:
1640 return -1;
1642 case MENU_ATTACHED_USB:
1643 return PLUGIN_USB_CONNECTED;
1645 default:
1646 return 0;
1652 Fill the config struct with some defaults
1654 void set_default_config(void)
1656 config.spacing_between_slides = 40;
1657 config.extra_spacing_for_center_slide = 0;
1658 config.show_slides = 3;
1659 config.avg_album_width = 0;
1660 config.zoom = 100;
1664 Read the config file.
1665 For now, the size has to match.
1666 Later a version number might be appropiate.
1668 bool read_pfconfig(void)
1670 set_default_config();
1671 /* defaults */
1672 int fh = rb->open( CONFIG_FILE, O_RDONLY );
1673 if ( fh < 0 ) { /* no config yet */
1674 return true;
1676 int ret = rb->read(fh, &config, sizeof(struct config_data));
1677 rb->close(fh);
1678 if ( ret != sizeof(struct config_data) ) {
1679 set_default_config();
1680 rb->splash(2*HZ, "Config invalid. Using defaults");
1682 return true;
1686 Write the config file
1688 bool write_pfconfig(void)
1690 int fh = rb->creat( CONFIG_FILE );
1691 if( fh < 0 ) return false;
1692 rb->write( fh, &config, sizeof( struct config_data ) );
1693 rb->close( fh );
1694 return true;
1698 Animation step for zooming into the current cover
1700 void update_cover_in_animation(void)
1702 cover_animation_keyframe++;
1703 if( cover_animation_keyframe < 20 ) {
1704 center_slide.distance-=5;
1705 center_slide.angle+=1;
1706 extra_fade += 13;
1708 else if( cover_animation_keyframe < 35 ) {
1709 center_slide.angle+=16;
1711 else {
1712 cover_animation_keyframe = 0;
1713 selected_track = 0;
1714 pf_state = pf_show_tracks;
1719 Animation step for zooming out the current cover
1721 void update_cover_out_animation(void)
1723 cover_animation_keyframe++;
1724 if( cover_animation_keyframe <= 15 ) {
1725 center_slide.angle-=16;
1727 else if( cover_animation_keyframe < 35 ) {
1728 center_slide.distance+=5;
1729 center_slide.angle-=1;
1730 extra_fade -= 13;
1732 else {
1733 cover_animation_keyframe = 0;
1734 pf_state = pf_idle;
1739 Draw a blue gradient at y with height h
1741 static inline void draw_gradient(int y, int h)
1743 static int r, inc, c;
1744 inc = (100 << 8) / h;
1745 c = 0;
1746 selected_track_pulse = (selected_track_pulse+1) % 10;
1747 int c2 = selected_track_pulse - 5;
1748 for (r=0; r<h; r++) {
1749 rb->lcd_set_foreground(LCD_RGBPACK(c2+80-(c >> 9), c2+100-(c >> 9),
1750 c2+250-(c >> 8)));
1751 rb->lcd_hline(0, LCD_WIDTH, r+y);
1752 if ( r > h/2 )
1753 c-=inc;
1754 else
1755 c+=inc;
1761 Reset the track list after a album change
1763 void reset_track_list(void)
1765 int albumtxt_w, albumtxt_h;
1766 const char* albumtxt = get_album_name(center_index);
1767 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1768 const int height = LCD_HEIGHT-albumtxt_h-10;
1769 track_list_visible_entries = fmin( height/albumtxt_h , track_count );
1770 start_index_track_list = 0;
1771 track_scroll_index = 0;
1772 track_scroll_dir = 1;
1773 selected_track = 0;
1777 Display the list of tracks
1779 void show_track_list(void)
1781 rb->lcd_clear_display();
1782 if ( center_slide.slide_index != track_index ) {
1783 create_track_index(center_slide.slide_index);
1784 reset_track_list();
1786 static int titletxt_w, titletxt_h, titletxt_y, titletxt_x, i, color;
1787 titletxt_y = 0;
1788 if (track_list_visible_entries >= track_count)
1790 int albumtxt_h;
1791 const char* albumtxt = get_album_name(center_index);
1792 rb->lcd_getstringsize(albumtxt, NULL, &albumtxt_h);
1793 titletxt_y = ((LCD_HEIGHT-albumtxt_h-10)-(track_count*albumtxt_h))/2;
1796 int track_i;
1797 for (i=0; i < track_list_visible_entries; i++) {
1798 track_i = i+start_index_track_list;
1799 rb->lcd_getstringsize(get_track_name(track_i), &titletxt_w, &titletxt_h);
1800 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
1801 if ( track_i == selected_track ) {
1802 draw_gradient(titletxt_y, titletxt_h);
1803 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1804 if (titletxt_w > LCD_WIDTH ) {
1805 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
1806 track_scroll_dir = 1;
1807 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
1808 track_scroll_index += track_scroll_dir*2;
1809 titletxt_x = track_scroll_index;
1811 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1813 else {
1814 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
1815 rb->lcd_set_foreground(LCD_RGBPACK(color,color,color));
1816 rb->lcd_putsxy(titletxt_x,titletxt_y,get_track_name(track_i));
1818 titletxt_y += titletxt_h;
1822 void select_next_track(void)
1824 if ( selected_track < track_count - 1 ) {
1825 selected_track++;
1826 track_scroll_index = 0;
1827 track_scroll_dir = 1;
1828 if (selected_track==(track_list_visible_entries+start_index_track_list))
1829 start_index_track_list++;
1833 void select_prev_track(void)
1835 if (selected_track > 0 ) {
1836 if (selected_track==start_index_track_list) start_index_track_list--;
1837 track_scroll_index = 0;
1838 track_scroll_dir = 1;
1839 selected_track--;
1844 Draw the current album name
1846 void draw_album_text(void)
1848 int albumtxt_w, albumtxt_h;
1849 int albumtxt_y = 0;
1851 char *albumtxt;
1852 int c;
1853 /* Draw album text */
1854 if ( pf_state == pf_scrolling ) {
1855 c = ((slide_frame & 0xffff )/ 255);
1856 if (step < 0) c = 255-c;
1857 if (c > 128 ) { /* half way to next slide .. still not perfect! */
1858 albumtxt = get_album_name(center_index+step);
1859 c = (c-128)*2;
1861 else {
1862 albumtxt = get_album_name(center_index);
1863 c = (128-c)*2;
1866 else {
1867 c= 255;
1868 albumtxt = get_album_name(center_index);
1871 rb->lcd_set_foreground(LCD_RGBPACK(c,c,c));
1872 rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h);
1873 if (center_index != prev_center_index) {
1874 albumtxt_x = 0;
1875 albumtxt_dir = -1;
1876 prev_center_index = center_index;
1878 albumtxt_y = LCD_HEIGHT-albumtxt_h-10;
1880 if (albumtxt_w > LCD_WIDTH ) {
1881 rb->lcd_putsxy(albumtxt_x, albumtxt_y , albumtxt);
1882 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
1883 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
1884 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
1885 albumtxt_x += albumtxt_dir;
1888 else {
1889 rb->lcd_putsxy((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
1897 Main function that also contain the main plasma
1898 algorithm.
1900 int main(void)
1902 int ret;
1903 draw_splashscreen();
1905 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
1906 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
1907 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
1908 return PLUGIN_ERROR;
1912 if (!read_pfconfig()) {
1913 rb->splash(HZ, "Error in config. Please delete " CONFIG_FILE);
1914 return PLUGIN_ERROR;
1917 if (!allocate_buffers()) {
1918 rb->splash(HZ, "Could not allocate temporary buffers");
1919 return PLUGIN_ERROR;
1922 ret = create_album_index();
1923 if (ret == ERROR_BUFFER_FULL) {
1924 rb->splash(HZ, "Not enough memory for album names");
1925 return PLUGIN_ERROR;
1926 } else if (ret == ERROR_NO_ALBUMS) {
1927 rb->splash(HZ, "No albums found. Please enable database");
1928 return PLUGIN_ERROR;
1931 if (!create_albumart_cache(config.avg_album_width == 0)) {
1932 rb->splash(HZ, "Could not create album art cache");
1933 return PLUGIN_ERROR;
1936 if (!create_empty_slide(false)) {
1937 rb->splash(HZ, "Could not load the empty slide");
1938 return PLUGIN_ERROR;
1941 if (!free_buffers()) {
1942 rb->splash(HZ, "Could not free temporary buffers");
1943 return PLUGIN_ERROR;
1946 if (!create_pf_thread()) {
1947 rb->splash(HZ, "Cannot create thread!");
1948 return PLUGIN_ERROR;
1951 int i;
1953 /* initialize */
1954 int min_slide_cache = fmin(number_of_slides, SLIDE_CACHE_SIZE);
1955 for (i = 0; i < min_slide_cache; i++) {
1956 cache[i].hid = -1;
1957 cache[i].touched = 0;
1958 slide_cache_stack[i] = SLIDE_CACHE_SIZE-i-1;
1960 slide_cache_stack_index = min_slide_cache-1;
1961 slide_cache_in_use = 0;
1962 buffer = rb->lcd_framebuffer;
1964 pf_state = pf_idle;
1966 track_index = -1;
1967 extra_fade = 0;
1968 center_index = 0;
1969 slide_frame = 0;
1970 step = 0;
1971 target = 0;
1972 fade = 256;
1973 show_fps = false;
1975 recalc_table();
1976 reset_slides();
1978 char fpstxt[10];
1979 int button;
1981 int frames = 0;
1982 long last_update = *rb->current_tick;
1983 long current_update;
1984 long update_interval = 100;
1985 int fps = 0;
1987 bool instant_update;
1988 old_drawmode = rb->lcd_get_drawmode();
1989 rb->lcd_set_drawmode(DRMODE_FG);
1990 while (true) {
1991 current_update = *rb->current_tick;
1992 frames++;
1994 /* Initial rendering */
1995 instant_update = false;
1997 /* Handle states */
1998 switch ( pf_state ) {
1999 case pf_scrolling:
2000 update_scroll_animation();
2001 render_all_slides();
2002 instant_update = true;
2003 break;
2004 case pf_cover_in:
2005 update_cover_in_animation();
2006 render_all_slides();
2007 instant_update = true;
2008 break;
2009 case pf_cover_out:
2010 update_cover_out_animation();
2011 render_all_slides();
2012 instant_update = true;
2013 break;
2014 case pf_show_tracks:
2015 show_track_list();
2016 break;
2017 case pf_idle:
2018 render_all_slides();
2019 break;
2022 /* Calculate FPS */
2023 if (current_update - last_update > update_interval) {
2024 fps = frames * HZ / (current_update - last_update);
2025 last_update = current_update;
2026 frames = 0;
2029 /* Draw FPS */
2030 if (show_fps) {
2031 rb->lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
2032 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2033 rb->lcd_putsxy(0, 0, fpstxt);
2036 draw_album_text();
2039 /* Copy offscreen buffer to LCD and give time to other threads */
2040 rb->lcd_update();
2041 rb->yield();
2043 /*/ Handle buttons */
2044 button = pluginlib_getaction(rb, instant_update ? 0 : HZ/16,
2045 plugin_contexts, NB_ACTION_CONTEXTS);
2047 switch (button) {
2048 case PICTUREFLOW_QUIT:
2049 return PLUGIN_OK;
2051 case PICTUREFLOW_MENU:
2052 if ( pf_state == pf_idle || pf_state == pf_scrolling ) {
2053 ret = main_menu();
2054 if ( ret == -1 ) return PLUGIN_OK;
2055 if ( ret != 0 ) return i;
2056 rb->lcd_set_drawmode(DRMODE_FG);
2058 else {
2059 pf_state = pf_cover_out;
2061 break;
2063 case PICTUREFLOW_NEXT_ALBUM:
2064 case PICTUREFLOW_NEXT_ALBUM_REPEAT:
2065 #ifdef SCROLLWHEEL
2066 if ( pf_state == pf_show_tracks )
2067 select_next_track();
2068 #endif
2069 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2070 show_next_slide();
2071 break;
2073 case PICTUREFLOW_PREV_ALBUM:
2074 case PICTUREFLOW_PREV_ALBUM_REPEAT:
2075 #ifdef SCROLLWHEEL
2076 if ( pf_state == pf_show_tracks )
2077 select_prev_track();
2078 #endif
2079 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2080 show_previous_slide();
2081 break;
2083 #ifndef SCROLLWHEEL
2084 case PICTUREFLOW_NEXT_TRACK:
2085 case PICTUREFLOW_NEXT_TRACK_REPEAT:
2086 if ( pf_state == pf_show_tracks )
2087 select_next_track();
2088 break;
2090 case PICTUREFLOW_PREV_TRACK:
2091 case PICTUREFLOW_PREV_TRACK_REPEAT:
2092 if ( pf_state == pf_show_tracks )
2093 select_prev_track();
2094 break;
2095 #endif
2097 case PICTUREFLOW_SELECT_ALBUM:
2098 if ( pf_state == pf_idle )
2099 pf_state = pf_cover_in;
2100 if ( pf_state == pf_show_tracks )
2101 pf_state = pf_cover_out;
2102 break;
2104 default:
2105 if (rb->default_event_handler_ex(button, cleanup, NULL)
2106 == SYS_USB_CONNECTED)
2107 return PLUGIN_USB_CONNECTED;
2108 break;
2115 /*************************** Plugin entry point ****************************/
2117 enum plugin_status plugin_start(const struct plugin_api *api, const void *parameter)
2119 int ret;
2121 rb = api; /* copy to global api pointer */
2122 (void) parameter;
2123 #if LCD_DEPTH > 1
2124 rb->lcd_set_backdrop(NULL);
2125 #endif
2126 /* Turn off backlight timeout */
2127 backlight_force_on(rb); /* backlight control in lib/helper.c */
2128 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2129 rb->cpu_boost(true);
2130 #endif
2131 ret = main();
2132 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2133 rb->cpu_boost(false);
2134 #endif
2135 if ( ret == PLUGIN_OK ) {
2136 if (!write_pfconfig()) {
2137 rb->splash(HZ, "Error writing config.");
2138 ret = PLUGIN_ERROR;
2142 end_pf_thread();
2143 cleanup(NULL);
2144 return ret;