1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
27 #include "lib/pluginlib_actions.h"
28 #include "lib/helper.h"
30 #include "lib/picture.h"
31 #include "pluginbitmaps/pictureflow_logo.h"
32 #include "pluginbitmaps/pictureflow_emptyslide.h"
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])
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)
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
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
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 */
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
94 #define PREFERRED_IMG_WIDTH 100
95 #define PREFERRED_IMG_HEIGHT 100
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"
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 */
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 */
165 struct load_slide_event_data
{
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
};
183 long avg_album_width
;
184 int spacing_between_slides
;
185 int extra_spacing_for_center_slide
;
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
;
204 static int center_index
; /* index of the slide that is in the center */
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
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
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
;
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))))
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
;
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
)
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)];
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
) );
372 rb
->tagcache_search(&tcs
, tag_album
);
373 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
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
;
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
;
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
) {
424 if (!rb
->tagcache_search(&tcs
, tag_title
))
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
);
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;
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
;
450 while (temp_titles
[track_num
][0] != '\0')
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
;
460 rb
->tagcache_search_finish(&tcs
);
461 track_index
= slide_index
;
463 /* now fix the track list order */
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;
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
))
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
) ) {
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
) )
515 else if ( rb
->search_albumart_files(&id3
, "", buf
, buflen
) )
521 /* did not find a matching track */
524 rb
->tagcache_search_finish(&tcs
);
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);
547 Draw a simple progress bar
549 void draw_progressbar(int step
)
552 const int bar_height
= 22;
553 const int w
= LCD_WIDTH
- 20;
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");
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));
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
);
586 if (rb
->bufgetdata(input_hid
, 0, (void *)&input_bmp_buffer
) < input_size
) {
587 rb
->bufclose(input_hid
);
591 output_hid
= rb
->bufalloc(NULL
, output_size
, TYPE_BITMAP
);
593 if (output_hid
< 0) {
594 rb
->bufclose(input_hid
);
598 if (rb
->bufgetdata(output_hid
, 0, (void *)&output_bmp_buffer
) < output_size
) {
599 rb
->bufclose(output_hid
);
607 Free the temporary buffers
609 bool free_buffers(void)
611 rb
->bufclose(input_hid
);
612 rb
->bufclose(output_hid
);
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
;
624 if ( ! force
&& rb
->file_exists( CACHE_PREFIX
"/ready" ) ) return true;
627 struct bitmap input_bmp
;
629 config
.avg_album_width
= 0;
630 for (i
=0; i
< album_count
; i
++)
633 if (!get_albumart_for_index_from_db(i
, tmp_path_name
, MAX_PATH
))
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
,
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
;
652 if ( rb
->button_get(false) == PICTUREFLOW_MENU
) return false;
655 rb
->splash(2*HZ
, "No albums found");
658 config
.avg_album_width
/= slides
;
659 if ( config
.avg_album_width
== 0 ) {
660 rb
->splash(HZ
, "album size is 0");
663 fh
= rb
->creat( CACHE_PREFIX
"/ready" );
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;
676 if ( slide_cache_stack
[i
] == slide_index
) return i
;
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
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
);
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
);
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
;
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
;
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
;
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
);
753 if ( slide_cache_stack_index
>= 0 )
754 result
= slide_cache_stack
[ slide_cache_stack_index
-- ];
757 rb
->mutex_unlock(&slide_cache_stack_lock
);
763 Load the slide into the cache.
764 Thus we have to queue the loading request in our thread while discarding the
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
779 long sleep_time
= 5 * HZ
;
780 struct queue_event ev
;
782 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
787 /* we just woke up */
791 while ( (slide_index
= slide_stack_pop()) != -1 ) {
792 load_surface( slide_index
);
793 rb
->queue_wait_w_tmo(&thread_q
, &ev
, HZ
/10);
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(
828 sizeof(thread_stack
),
830 "Picture load thread"
831 IF_PRIO(, PRIORITY_BACKGROUND
)
837 thread_is_running
= 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
) );
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
);
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
));
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
);
884 if (rb
->bufgetdata(hid
, 0, (void *)&bm
) < size
) {
889 bm
->width
= bmph
.width
;
890 bm
->height
= bmph
.height
;
891 bm
->format
= FORMAT_NATIVE
;
892 bm
->data
= ((unsigned char *)bm
+ sizeof(struct bitmap
));
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
);
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
;
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
);
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
;
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",
974 int hid
= read_pfraw(tmp_path_name
);
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
;
990 Load the surface from a bmp and overwrite the oldest slide in the cache
993 int load_surface(const int slide_index
)
995 long oldest_tick
= *rb
->current_tick
;
996 int oldest_slide
= 0;
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
++);
1003 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) { /* look for oldest slide */
1004 if (cache
[i
].touched
< oldest_tick
) {
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
)
1029 ssize_t ret
= rb
->bufgetdata(hid
, 0, (void *)&bmp
);
1038 Return the requested surface
1040 static inline struct bitmap
*surface(const int slide_index
)
1042 if (slide_index
< 0)
1044 if (slide_index
>= number_of_slides
)
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
;
1071 for (i
= 0; i
< config
.show_slides
; i
++) {
1072 struct slide_data
*si
= &left_slides
[i
];
1074 si
->cx
= -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
);
1076 si
->slide_index
= center_index
- 1 - i
;
1080 for (i
= 0; i
< config
.show_slides
; i
++) {
1081 struct slide_data
*si
= &right_slides
[i
];
1083 si
->cx
= offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
;
1085 si
->slide_index
= center_index
+ 1 + i
;
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;
1100 for (i
= 0; i
< w
; i
++) {
1101 PFreal gg
= (PFREAL_HALF
+ i
* PFREAL_ONE
) / (2 * h
);
1102 rays
[w
- i
- 1] = -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
)
1124 unsigned int p
= ((((c
|(c
<<16)) & 0x07e0f81f) * a
) >> 5) & 0x07e0f81f;
1125 return swap16( (fb_data
) (p
| ( p
>> 16 )) );
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 ));
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
);
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
;
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
);
1186 result_rect
->left
= xi
;
1188 for (x
= fmax(xi
, col1
); x
<= col2
; x
++) {
1190 PFreal fk
= rays
[x
];
1192 fk
= fk
- fdiv(sdx
, sdy
);
1193 hity
= -fdiv(( rays
[x
] * distance
1195 + slide
->cy
* sdx
/ sdy
), fk
);
1198 dist
= distance
* PFREAL_ONE
+ hity
;
1202 PFreal hitx
= fmul(dist
, rays
[x
]);
1204 PFreal hitdist
= fdiv(hitx
- slide
->cx
, sdx
);
1206 const int column
= (sw
>> 1) + (hitdist
>> PFREAL_SHIFT
);
1213 result_rect
->right
= x
;
1215 result_rect
->left
= x
;
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);
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
];
1232 while ((y1
>= 0) && (y2
< h
) && (p1
>= 0)) {
1233 *pixel1
= ptr
[p1
>> PFREAL_SHIFT
];
1234 *pixel2
= ptr
[p2
>> PFREAL_SHIFT
];
1239 pixel1
-= pixelstep
;
1240 pixel2
+= pixelstep
;
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
);
1249 pixel1
-= pixelstep
;
1250 pixel2
+= pixelstep
;
1253 /* let the music play... */
1256 result_rect
->top
= 0;
1257 result_rect
->bottom
= h
- 1;
1263 Jump the the given slide_index
1265 static inline void set_current_slide(const int slide_index
)
1268 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1269 target
= center_index
;
1270 slide_frame
= slide_index
<< 16;
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)
1289 if (center_index
> 0) {
1290 target
= center_index
- 1;
1293 } else if ( step
> 0 ) {
1294 target
= center_index
;
1297 target
= fmax(0, center_index
- 2);
1303 Go to the next slide
1305 void show_next_slide(void)
1308 if (center_index
< number_of_slides
- 1) {
1309 target
= center_index
+ 1;
1312 } else if ( step
< 0 ) {
1313 target
= center_index
;
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
;
1343 r
.left
= LCD_WIDTH
; r
.top
= 0; r
.bottom
= 0; r
.right
= 0;
1344 render_slide(¢er_slide
, &r
, 256, -1, -1);
1346 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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
)) {
1360 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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,
1371 if (!is_empty_rect(&r
)) {
1373 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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
++) {
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
)) {
1393 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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,
1409 if (!is_empty_rect(&r
)) {
1411 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1421 Updates the animation effect. Call this periodically from a timer.
1423 void update_scroll_animation(void)
1431 /* deaccelerate when approaching the target */
1433 const int max
= 2 * 65536;
1435 int fi
= slide_frame
;
1436 fi
-= (target
<< 16);
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 */
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
) {
1480 for (i
= 0; i
< config
.show_slides
; i
++) {
1481 struct slide_data
*si
= &left_slides
[i
];
1484 -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
+ step
1485 * config
.spacing_between_slides
* ftick
);
1489 for (i
= 0; i
< config
.show_slides
; i
++) {
1490 struct slide_data
*si
= &right_slides
[i
];
1493 offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
- step
1494 * config
.spacing_between_slides
* ftick
;
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
);
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 ? */
1523 void cleanup(void *parameter
)
1526 /* Turn on backlight timeout (revert to settings) */
1527 backlight_use_settings(rb
); /* backlight control in lib/helper.c */
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;
1561 Shows the settings menu
1563 int settings_menu(void) {
1566 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1567 "Spacing", "Center margin", "Number of slides", "Zoom",
1571 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1574 rb
->set_bool("Show FPS", &(config
.show_fps
));
1578 rb
->set_int("Spacing between slides", "", 1,
1579 &(config
.spacing_between_slides
),
1580 NULL
, 1, 0, 100, NULL
);
1586 rb
->set_int("Center margin", "", 1,
1587 &(config
.extra_spacing_for_center_slide
),
1588 NULL
, 1, -50, 50, NULL
);
1594 rb
->set_int("Number of slides", "", 1, &(config
.show_slides
),
1595 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
1601 rb
->set_int("Zoom", "", 1, &(config
.zoom
),
1602 NULL
, 1, 10, 300, NULL
);
1607 rb
->remove(CACHE_PREFIX
"/ready");
1608 rb
->remove(EMPTY_SLIDE
);
1609 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
1612 case MENU_ATTACHED_USB
:
1613 return PLUGIN_USB_CONNECTED
;
1615 } while ( selection
>= 0 );
1627 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1629 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
1630 "Settings", "Return", "Quit");
1633 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
1635 result
= settings_menu();
1636 if ( result
!= 0 ) return result
;
1645 case MENU_ATTACHED_USB
:
1646 return PLUGIN_USB_CONNECTED
;
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;
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();
1678 int fh
= rb
->open( CONFIG_FILE
, O_RDONLY
);
1679 if ( fh
< 0 ) { /* no config yet */
1682 int ret
= rb
->read(fh
, &config
, sizeof(struct config_data
));
1684 if ( ret
!= sizeof(struct config_data
) ) {
1685 set_default_config();
1686 rb
->splash(2*HZ
, "Config invalid. Using defaults");
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
) );
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;
1714 else if( cover_animation_keyframe
< 35 ) {
1715 center_slide
.angle
+=16;
1718 cover_animation_keyframe
= 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;
1739 cover_animation_keyframe
= 0;
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
;
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),
1757 rb
->lcd_hline(0, LCD_WIDTH
, r
+y
);
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
);
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;
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
);
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
)
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;
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
));
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 ) {
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;
1855 Draw the current album name
1857 void draw_album_text(void)
1859 int albumtxt_w
, albumtxt_h
;
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
);
1873 albumtxt
= get_album_name(center_index
);
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
) {
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
;
1900 rb
->lcd_putsxy((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
1908 Main function that also contain the main plasma
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
;
1965 int min_slide_cache
= fmin(number_of_slides
, SLIDE_CACHE_SIZE
);
1966 for (i
= 0; i
< min_slide_cache
; i
++) {
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
;
1992 long last_update
= *rb
->current_tick
;
1993 long current_update
;
1994 long update_interval
= 100;
1997 bool instant_update
;
1998 old_drawmode
= rb
->lcd_get_drawmode();
1999 rb
->lcd_set_drawmode(DRMODE_FG
);
2001 current_update
= *rb
->current_tick
;
2004 /* Initial rendering */
2005 instant_update
= false;
2008 switch ( pf_state
) {
2010 update_scroll_animation();
2011 render_all_slides();
2012 instant_update
= true;
2015 update_cover_in_animation();
2016 render_all_slides();
2017 instant_update
= true;
2020 update_cover_out_animation();
2021 render_all_slides();
2022 instant_update
= true;
2024 case pf_show_tracks
:
2028 render_all_slides();
2033 if (current_update
- last_update
> update_interval
) {
2034 fps
= frames
* HZ
/ (current_update
- last_update
);
2035 last_update
= current_update
;
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
);
2049 /* Copy offscreen buffer to LCD and give time to other threads */
2053 /*/ Handle buttons */
2054 button
= pluginlib_getaction(rb
, instant_update
? 0 : HZ
/16,
2055 plugin_contexts
, NB_ACTION_CONTEXTS
);
2058 case PICTUREFLOW_QUIT
:
2061 case PICTUREFLOW_MENU
:
2062 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
) {
2064 if ( ret
== -1 ) return PLUGIN_OK
;
2065 if ( ret
!= 0 ) return i
;
2066 rb
->lcd_set_drawmode(DRMODE_FG
);
2069 pf_state
= pf_cover_out
;
2073 case PICTUREFLOW_NEXT_ALBUM
:
2074 case PICTUREFLOW_NEXT_ALBUM_REPEAT
:
2076 if ( pf_state
== pf_show_tracks
)
2077 select_next_track();
2079 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2083 case PICTUREFLOW_PREV_ALBUM
:
2084 case PICTUREFLOW_PREV_ALBUM_REPEAT
:
2086 if ( pf_state
== pf_show_tracks
)
2087 select_prev_track();
2089 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2090 show_previous_slide();
2094 case PICTUREFLOW_NEXT_TRACK
:
2095 case PICTUREFLOW_NEXT_TRACK_REPEAT
:
2096 if ( pf_state
== pf_show_tracks
)
2097 select_next_track();
2100 case PICTUREFLOW_PREV_TRACK
:
2101 case PICTUREFLOW_PREV_TRACK_REPEAT
:
2102 if ( pf_state
== pf_show_tracks
)
2103 select_prev_track();
2107 case PICTUREFLOW_SELECT_ALBUM
:
2108 if ( pf_state
== pf_idle
) {
2110 pf_state
= pf_cover_in
;
2112 if ( pf_state
== pf_show_tracks
)
2113 pf_state
= pf_cover_out
;
2117 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2118 == SYS_USB_CONNECTED
)
2119 return PLUGIN_USB_CONNECTED
;
2127 /*************************** Plugin entry point ****************************/
2129 enum plugin_status
plugin_start(const struct plugin_api
*api
, const void *parameter
)
2133 rb
= api
; /* copy to global api pointer */
2136 rb
->lcd_set_backdrop(NULL
);
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);
2144 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2145 rb
->cpu_boost(false);
2147 if ( ret
== PLUGIN_OK
) {
2148 if (!write_pfconfig()) {
2149 rb
->splash(HZ
, "Error writing config.");