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 "pluginlib_actions.h"
31 #include "pictureflow_logo.h"
32 #include "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
},
181 long avg_album_width
;
182 int spacing_between_slides
;
183 int extra_spacing_for_center_slide
;
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
;
199 static int center_index
; /* index of the slide that is in the center */
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
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
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
;
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))))
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
;
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
)
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)];
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
) );
368 rb
->tagcache_search(&tcs
, tag_album
);
369 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
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
;
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
;
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
) {
420 if (!rb
->tagcache_search(&tcs
, tag_title
))
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
);
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;
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
;
446 while (temp_titles
[track_num
][0] != '\0')
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
;
456 rb
->tagcache_search_finish(&tcs
);
457 track_index
= slide_index
;
459 /* now fix the track list order */
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;
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
))
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
) ) {
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
) )
511 else if ( rb
->search_albumart_files(&id3
, "", buf
, buflen
) )
517 /* did not find a matching track */
520 rb
->tagcache_search_finish(&tcs
);
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);
543 Draw a simple progress bar
545 void draw_progressbar(int step
)
548 const int bar_height
= 22;
549 const int w
= LCD_WIDTH
- 20;
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");
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));
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
);
582 if (rb
->bufgetdata(input_hid
, 0, (void *)&input_bmp_buffer
) < input_size
) {
583 rb
->bufclose(input_hid
);
587 output_hid
= rb
->bufalloc(NULL
, output_size
, TYPE_BITMAP
);
589 if (output_hid
< 0) {
590 rb
->bufclose(input_hid
);
594 if (rb
->bufgetdata(output_hid
, 0, (void *)&output_bmp_buffer
) < output_size
) {
595 rb
->bufclose(output_hid
);
603 Free the temporary buffers
605 bool free_buffers(void)
607 rb
->bufclose(input_hid
);
608 rb
->bufclose(output_hid
);
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
;
620 if ( ! force
&& rb
->file_exists( CACHE_PREFIX
"/ready" ) ) return true;
623 struct bitmap input_bmp
;
625 config
.avg_album_width
= 0;
626 for (i
=0; i
< album_count
; i
++)
629 if (!get_albumart_for_index_from_db(i
, tmp_path_name
, MAX_PATH
))
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
,
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
;
648 if ( rb
->button_get(false) == PICTUREFLOW_MENU
) return false;
651 rb
->splash(2*HZ
, "No albums found");
654 config
.avg_album_width
/= slides
;
655 if ( config
.avg_album_width
== 0 ) {
656 rb
->splash(HZ
, "album size is 0");
659 fh
= rb
->creat( CACHE_PREFIX
"/ready" );
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;
672 if ( slide_cache_stack
[i
] == slide_index
) return i
;
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
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
);
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
);
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
;
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
;
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
;
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
);
749 if ( slide_cache_stack_index
>= 0 )
750 result
= slide_cache_stack
[ slide_cache_stack_index
-- ];
753 rb
->mutex_unlock(&slide_cache_stack_lock
);
759 Load the slide into the cache.
760 Thus we have to queue the loading request in our thread while discarding the
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
775 long sleep_time
= 5 * HZ
;
776 struct queue_event ev
;
778 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
783 /* we just woke up */
787 while ( (slide_index
= slide_stack_pop()) != -1 ) {
788 load_surface( slide_index
);
789 rb
->queue_wait_w_tmo(&thread_q
, &ev
, HZ
/10);
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(
824 sizeof(thread_stack
),
826 "Picture load thread"
827 IF_PRIO(, PRIORITY_BACKGROUND
)
833 thread_is_running
= 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
) );
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
);
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
));
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
);
880 if (rb
->bufgetdata(hid
, 0, (void *)&bm
) < size
) {
885 bm
->width
= bmph
.width
;
886 bm
->height
= bmph
.height
;
887 bm
->format
= FORMAT_NATIVE
;
888 bm
->data
= ((unsigned char *)bm
+ sizeof(struct bitmap
));
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
);
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
;
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
);
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
;
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",
970 int hid
= read_pfraw(tmp_path_name
);
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
;
986 Load the surface from a bmp and overwrite the oldest slide in the cache
989 int load_surface(const int slide_index
)
991 long oldest_tick
= *rb
->current_tick
;
992 int oldest_slide
= 0;
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
++);
999 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) { /* look for oldest slide */
1000 if (cache
[i
].touched
< oldest_tick
) {
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
)
1025 ssize_t ret
= rb
->bufgetdata(hid
, 0, (void *)&bmp
);
1034 Return the requested surface
1036 static inline struct bitmap
*surface(const int slide_index
)
1038 if (slide_index
< 0)
1040 if (slide_index
>= number_of_slides
)
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
;
1067 for (i
= 0; i
< config
.show_slides
; i
++) {
1068 struct slide_data
*si
= &left_slides
[i
];
1070 si
->cx
= -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
);
1072 si
->slide_index
= center_index
- 1 - i
;
1076 for (i
= 0; i
< config
.show_slides
; i
++) {
1077 struct slide_data
*si
= &right_slides
[i
];
1079 si
->cx
= offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
;
1081 si
->slide_index
= center_index
+ 1 + i
;
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;
1096 for (i
= 0; i
< w
; i
++) {
1097 PFreal gg
= (PFREAL_HALF
+ i
* PFREAL_ONE
) / (2 * h
);
1098 rays
[w
- i
- 1] = -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
)
1120 unsigned int p
= ((((c
|(c
<<16)) & 0x07e0f81f) * a
) >> 5) & 0x07e0f81f;
1121 return swap16( (fb_data
) (p
| ( p
>> 16 )) );
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 ));
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
);
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
;
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
);
1182 result_rect
->left
= xi
;
1184 for (x
= fmax(xi
, col1
); x
<= col2
; x
++) {
1186 PFreal fk
= rays
[x
];
1188 fk
= fk
- fdiv(sdx
, sdy
);
1189 hity
= -fdiv(( rays
[x
] * distance
1191 + slide
->cy
* sdx
/ sdy
), fk
);
1194 dist
= distance
* PFREAL_ONE
+ hity
;
1198 PFreal hitx
= fmul(dist
, rays
[x
]);
1200 PFreal hitdist
= fdiv(hitx
- slide
->cx
, sdx
);
1202 const int column
= (sw
>> 1) + (hitdist
>> PFREAL_SHIFT
);
1209 result_rect
->right
= x
;
1211 result_rect
->left
= x
;
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);
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
];
1228 while ((y1
>= 0) && (y2
< h
) && (p1
>= 0)) {
1229 *pixel1
= ptr
[p1
>> PFREAL_SHIFT
];
1230 *pixel2
= ptr
[p2
>> PFREAL_SHIFT
];
1235 pixel1
-= pixelstep
;
1236 pixel2
+= pixelstep
;
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
);
1245 pixel1
-= pixelstep
;
1246 pixel2
+= pixelstep
;
1249 /* let the music play... */
1252 result_rect
->top
= 0;
1253 result_rect
->bottom
= h
- 1;
1259 Jump the the given slide_index
1261 static inline void set_current_slide(const int slide_index
)
1264 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1265 target
= center_index
;
1266 slide_frame
= slide_index
<< 16;
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)
1285 if (center_index
> 0) {
1286 target
= center_index
- 1;
1289 } else if ( step
> 0 ) {
1290 target
= center_index
;
1293 target
= fmax(0, center_index
- 2);
1299 Go to the next slide
1301 void show_next_slide(void)
1304 if (center_index
< number_of_slides
- 1) {
1305 target
= center_index
+ 1;
1308 } else if ( step
< 0 ) {
1309 target
= center_index
;
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
;
1339 r
.left
= LCD_WIDTH
; r
.top
= 0; r
.bottom
= 0; r
.right
= 0;
1340 render_slide(¢er_slide
, &r
, 256, -1, -1);
1342 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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
)) {
1356 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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,
1367 if (!is_empty_rect(&r
)) {
1369 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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
++) {
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
)) {
1389 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
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,
1405 if (!is_empty_rect(&r
)) {
1407 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1417 Updates the animation effect. Call this periodically from a timer.
1419 void update_scroll_animation(void)
1427 /* deaccelerate when approaching the target */
1429 const int max
= 2 * 65536;
1431 int fi
= slide_frame
;
1432 fi
-= (target
<< 16);
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 */
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
) {
1476 for (i
= 0; i
< config
.show_slides
; i
++) {
1477 struct slide_data
*si
= &left_slides
[i
];
1480 -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
+ step
1481 * config
.spacing_between_slides
* ftick
);
1485 for (i
= 0; i
< config
.show_slides
; i
++) {
1486 struct slide_data
*si
= &right_slides
[i
];
1489 offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
- step
1490 * config
.spacing_between_slides
* ftick
;
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
);
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 ? */
1519 void cleanup(void *parameter
)
1522 /* Turn on backlight timeout (revert to settings) */
1523 backlight_use_settings(rb
); /* backlight control in lib/helper.c */
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;
1557 Shows the settings menu
1559 int settings_menu(void) {
1562 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1563 "Spacing", "Center margin", "Number of slides", "Zoom",
1567 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1570 rb
->set_bool("Show FPS", &show_fps
);
1574 rb
->set_int("Spacing between slides", "", 1,
1575 &(config
.spacing_between_slides
),
1576 NULL
, 1, 0, 100, NULL
);
1582 rb
->set_int("Center margin", "", 1,
1583 &(config
.extra_spacing_for_center_slide
),
1584 NULL
, 1, -50, 50, NULL
);
1590 rb
->set_int("Number of slides", "", 1, &(config
.show_slides
),
1591 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
1597 rb
->set_int("Number of slides", "", 1, &(config
.zoom
),
1598 NULL
, 1, 10, 300, NULL
);
1604 rb
->remove(CACHE_PREFIX
"/ready");
1605 rb
->remove(EMPTY_SLIDE
);
1606 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
1609 case MENU_ATTACHED_USB
:
1610 return PLUGIN_USB_CONNECTED
;
1612 } while ( selection
>= 0 );
1624 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1626 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
1627 "Settings", "Return", "Quit");
1630 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
1632 result
= settings_menu();
1633 if ( result
!= 0 ) return result
;
1642 case MENU_ATTACHED_USB
:
1643 return PLUGIN_USB_CONNECTED
;
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;
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();
1672 int fh
= rb
->open( CONFIG_FILE
, O_RDONLY
);
1673 if ( fh
< 0 ) { /* no config yet */
1676 int ret
= rb
->read(fh
, &config
, sizeof(struct config_data
));
1678 if ( ret
!= sizeof(struct config_data
) ) {
1679 set_default_config();
1680 rb
->splash(2*HZ
, "Config invalid. Using defaults");
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
) );
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;
1708 else if( cover_animation_keyframe
< 35 ) {
1709 center_slide
.angle
+=16;
1712 cover_animation_keyframe
= 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;
1733 cover_animation_keyframe
= 0;
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
;
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),
1751 rb
->lcd_hline(0, LCD_WIDTH
, r
+y
);
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;
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
);
1786 static int titletxt_w
, titletxt_h
, titletxt_y
, titletxt_x
, i
, color
;
1788 if (track_list_visible_entries
>= track_count
)
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;
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
));
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 ) {
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;
1844 Draw the current album name
1846 void draw_album_text(void)
1848 int albumtxt_w
, albumtxt_h
;
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
);
1862 albumtxt
= get_album_name(center_index
);
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
) {
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
;
1889 rb
->lcd_putsxy((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
1897 Main function that also contain the main plasma
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
;
1954 int min_slide_cache
= fmin(number_of_slides
, SLIDE_CACHE_SIZE
);
1955 for (i
= 0; i
< min_slide_cache
; i
++) {
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
;
1982 long last_update
= *rb
->current_tick
;
1983 long current_update
;
1984 long update_interval
= 100;
1987 bool instant_update
;
1988 old_drawmode
= rb
->lcd_get_drawmode();
1989 rb
->lcd_set_drawmode(DRMODE_FG
);
1991 current_update
= *rb
->current_tick
;
1994 /* Initial rendering */
1995 instant_update
= false;
1998 switch ( pf_state
) {
2000 update_scroll_animation();
2001 render_all_slides();
2002 instant_update
= true;
2005 update_cover_in_animation();
2006 render_all_slides();
2007 instant_update
= true;
2010 update_cover_out_animation();
2011 render_all_slides();
2012 instant_update
= true;
2014 case pf_show_tracks
:
2018 render_all_slides();
2023 if (current_update
- last_update
> update_interval
) {
2024 fps
= frames
* HZ
/ (current_update
- last_update
);
2025 last_update
= current_update
;
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
);
2039 /* Copy offscreen buffer to LCD and give time to other threads */
2043 /*/ Handle buttons */
2044 button
= pluginlib_getaction(rb
, instant_update
? 0 : HZ
/16,
2045 plugin_contexts
, NB_ACTION_CONTEXTS
);
2048 case PICTUREFLOW_QUIT
:
2051 case PICTUREFLOW_MENU
:
2052 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
) {
2054 if ( ret
== -1 ) return PLUGIN_OK
;
2055 if ( ret
!= 0 ) return i
;
2056 rb
->lcd_set_drawmode(DRMODE_FG
);
2059 pf_state
= pf_cover_out
;
2063 case PICTUREFLOW_NEXT_ALBUM
:
2064 case PICTUREFLOW_NEXT_ALBUM_REPEAT
:
2066 if ( pf_state
== pf_show_tracks
)
2067 select_next_track();
2069 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2073 case PICTUREFLOW_PREV_ALBUM
:
2074 case PICTUREFLOW_PREV_ALBUM_REPEAT
:
2076 if ( pf_state
== pf_show_tracks
)
2077 select_prev_track();
2079 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2080 show_previous_slide();
2084 case PICTUREFLOW_NEXT_TRACK
:
2085 case PICTUREFLOW_NEXT_TRACK_REPEAT
:
2086 if ( pf_state
== pf_show_tracks
)
2087 select_next_track();
2090 case PICTUREFLOW_PREV_TRACK
:
2091 case PICTUREFLOW_PREV_TRACK_REPEAT
:
2092 if ( pf_state
== pf_show_tracks
)
2093 select_prev_track();
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
;
2105 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2106 == SYS_USB_CONNECTED
)
2107 return PLUGIN_USB_CONNECTED
;
2115 /*************************** Plugin entry point ****************************/
2117 enum plugin_status
plugin_start(const struct plugin_api
*api
, const void *parameter
)
2121 rb
= api
; /* copy to global api pointer */
2124 rb
->lcd_set_backdrop(NULL
);
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);
2132 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2133 rb
->cpu_boost(false);
2135 if ( ret
== PLUGIN_OK
) {
2136 if (!write_pfconfig()) {
2137 rb
->splash(HZ
, "Error writing config.");