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 * All files in this archive are subject to the GNU General Public License.
17 * See the file COPYING in the source tree root for full license agreement.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ****************************************************************************/
25 #include "pluginlib_actions.h"
29 #include "pictureflow_logo.h"
30 #include "pictureflow_emptyslide.h"
35 /******************************* Globals ***********************************/
37 static const struct plugin_api
*rb
; /* global api struct pointer */
39 const struct button_mapping
*plugin_contexts
[]
40 = {generic_actions
, generic_directions
};
42 #define NB_ACTION_CONTEXTS sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
45 #if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
46 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
47 || (CONFIG_KEYPAD == IPOD_4G_PAD) \
48 || (CONFIG_KEYPAD == SANSA_E200_PAD)
53 #define PICTUREFLOW_NEXT_ALBUM PLA_DOWN
54 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_DOWN_REPEAT
55 #define PICTUREFLOW_PREV_ALBUM PLA_UP
56 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_UP_REPEAT
58 #define PICTUREFLOW_NEXT_ALBUM PLA_RIGHT
59 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_RIGHT_REPEAT
60 #define PICTUREFLOW_PREV_ALBUM PLA_LEFT
61 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_LEFT_REPEAT
62 #define PICTUREFLOW_NEXT_TRACK PLA_DOWN
63 #define PICTUREFLOW_NEXT_TRACK_REPEAT PLA_DOWN_REPEAT
64 #define PICTUREFLOW_PREV_TRACK PLA_UP
65 #define PICTUREFLOW_PREV_TRACK_REPEAT PLA_UP_REPEAT
67 #define PICTUREFLOW_MENU PLA_MENU
68 #define PICTUREFLOW_QUIT PLA_QUIT
69 #define PICTUREFLOW_SELECT_ALBUM PLA_FIRE
72 /* for fixed-point arithmetic, we need minimum 32-bit long
73 long long (64-bit) might be useful for multiplication and division */
75 #define PFREAL_SHIFT 10
76 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
77 #define PFREAL_ONE (1 << PFREAL_SHIFT)
78 #define PFREAL_HALF (PFREAL_ONE >> 1)
81 #define IANGLE_MAX 1024
82 #define IANGLE_MASK 1023
84 /* maximum size of an slide */
85 #define MAX_IMG_WIDTH LCD_WIDTH
86 #define MAX_IMG_HEIGHT LCD_HEIGHT
88 #if (LCD_HEIGHT < 100)
89 #define PREFERRED_IMG_WIDTH 50
90 #define PREFERRED_IMG_HEIGHT 50
92 #define PREFERRED_IMG_WIDTH 100
93 #define PREFERRED_IMG_HEIGHT 100
96 #define BUFFER_WIDTH LCD_WIDTH
97 #define BUFFER_HEIGHT LCD_HEIGHT
99 #define SLIDE_CACHE_SIZE 100
101 #define MAX_SLIDES_COUNT 10
103 #define SPACING_BETWEEN_SLIDE 40
104 #define EXTRA_SPACING_FOR_CENTER_SLIDE 0
106 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
107 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
110 #define EV_WAKEUP 1337
112 /* maximum number of albums */
113 #define MAX_ALBUMS 1024
114 #define AVG_ALBUM_NAME_LENGTH 20
116 #define MAX_TRACKS 50
117 #define AVG_TRACK_NAME_LENGTH 20
120 #define UNIQBUF_SIZE (64*1024)
122 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
123 #define CONFIG_FILE CACHE_PREFIX "/pictureflow.config"
125 /* Error return values */
126 #define ERROR_NO_ALBUMS -1
127 #define ERROR_BUFFER_FULL -2
130 /** structs we use */
141 int index
; /* index of the cached slide */
142 int hid
; /* handle ID of the cached slide */
143 long touched
; /* last time the slide was touched */
163 struct load_slide_event_data
{
169 struct pfraw_header
{
170 int32_t width
; /* bmap width in pixels */
171 int32_t height
; /* bmap height in pixels */
174 const struct picture logos
[]={
175 {pictureflow_logo
, BMPWIDTH_pictureflow_logo
, BMPHEIGHT_pictureflow_logo
},
179 long avg_album_width
;
180 int spacing_between_slides
;
181 int extra_spacing_for_center_slide
;
186 /** below we allocate the memory we want to use **/
188 static fb_data
*buffer
; /* for now it always points to the lcd framebuffer */
189 static PFreal rays
[BUFFER_WIDTH
];
190 static struct slide_data center_slide
;
191 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
192 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
193 static int slide_frame
;
197 static int center_index
; /* index of the slide that is in the center */
199 static PFreal offsetX
;
200 static PFreal offsetY
;
201 static bool show_fps
; /* show fps in the main screen */
202 static int number_of_slides
;
204 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
205 static int slide_cache_in_use
;
207 /* use long for aligning */
208 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
209 static int slide_cache_stack
[SLIDE_CACHE_SIZE
]; /* queue (as array) for scheduling load_surface */
210 static int slide_cache_stack_index
;
211 struct mutex slide_cache_stack_lock
;
213 static int empty_slide_hid
;
215 struct thread_entry
*thread_id
;
216 struct event_queue thread_q
;
218 static char tmp_path_name
[MAX_PATH
];
220 static long uniqbuf
[UNIQBUF_SIZE
];
221 static struct tagcache_search tcs
;
223 static struct album_data album
[MAX_ALBUMS
];
224 static char album_names
[MAX_ALBUMS
*AVG_ALBUM_NAME_LENGTH
];
225 static int album_count
;
227 static char track_names
[MAX_TRACKS
* AVG_TRACK_NAME_LENGTH
];
228 static struct track_data tracks
[MAX_TRACKS
];
229 static int track_count
;
230 static int track_index
;
231 static int selected_track
;
232 static int selected_track_pulse
;
234 static fb_data
*input_bmp_buffer
;
235 static fb_data
*output_bmp_buffer
;
236 static int input_hid
;
237 static int output_hid
;
238 static struct config_data config
;
240 static int old_drawmode
;
242 static bool thread_is_running
;
244 static int cover_animation_keyframe
;
245 static int extra_fade
;
247 static int albumtxt_x
= 0;
248 static int albumtxt_dir
= -1;
249 static int prev_center_index
= -1;
251 static int start_index_track_list
= 0;
252 static int track_list_visible_entries
= 0;
253 static int track_scroll_index
= 0;
254 static int track_scroll_dir
= 1;
257 Proposals for transitions:
259 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
260 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
262 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
264 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
267 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
268 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
270 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
284 bool create_bmp(struct bitmap
* input_bmp
, char *target_path
, bool resize
);
285 int load_surface(int);
287 static inline PFreal
fmul(PFreal a
, PFreal b
)
289 return (a
*b
) >> PFREAL_SHIFT
;
292 /* There are some precision issues when not using (long long) which in turn
293 takes very long to compute... I guess the best solution would be to optimize
294 the computations so it only requires a single long */
295 static inline PFreal
fdiv(PFreal num
, PFreal den
)
297 long long p
= (long long) (num
) << (PFREAL_SHIFT
* 2);
298 long long q
= p
/ (long long) den
;
299 long long r
= q
>> PFREAL_SHIFT
;
304 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
305 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
306 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
310 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
311 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
313 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
314 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
316 static inline PFreal
fmul(PFreal a
, PFreal b
)
318 return (a
*b
) >> PFREAL_SHIFT
;
321 static inline PFreal
fdiv(PFreal n
, PFreal m
)
323 return (n
<<(PFREAL_SHIFT
))/m
;
327 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
328 static const PFreal sin_tab
[] = {
329 3, 103, 202, 300, 394, 485, 571, 652,
330 726, 793, 853, 904, 947, 980, 1004, 1019,
331 1023, 1018, 1003, 978, 944, 901, 849, 789,
332 721, 647, 566, 479, 388, 294, 196, 97,
333 -4, -104, -203, -301, -395, -486, -572, -653,
334 -727, -794, -854, -905, -948, -981, -1005, -1020,
335 -1024, -1019, -1004, -979, -945, -902, -850, -790,
336 -722, -648, -567, -480, -389, -295, -197, -98,
340 static inline PFreal
fsin(int iangle
)
343 iangle
+= IANGLE_MAX
;
344 iangle
&= IANGLE_MASK
;
346 int i
= (iangle
>> 4);
347 PFreal p
= sin_tab
[i
];
348 PFreal q
= sin_tab
[(i
+1)];
350 return p
+ g
* (iangle
-i
*16)/16;
353 static inline PFreal
fcos(int iangle
)
355 return fsin(iangle
+ (IANGLE_MAX
>> 2));
359 Create an index of all albums from the database.
360 Also store the album names so we can access them later.
362 int create_album_index(void)
364 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
366 rb
->tagcache_search(&tcs
, tag_album
);
367 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
369 album
[0].name_idx
= 0;
370 while (rb
->tagcache_get_next(&tcs
) && album_count
< MAX_ALBUMS
)
372 l
= rb
->strlen(tcs
.result
) + 1;
373 if ( album_count
> 0 )
374 album
[album_count
].name_idx
= album
[album_count
-1].name_idx
+ old_l
;
376 if ( (album
[album_count
].name_idx
+ l
) > MAX_ALBUMS
*AVG_ALBUM_NAME_LENGTH
)
377 /* not enough memory */
378 return ERROR_BUFFER_FULL
;
380 rb
->strcpy(album_names
+ album
[album_count
].name_idx
, tcs
.result
);
381 album
[album_count
].seek
= tcs
.result_seek
;
385 rb
->tagcache_search_finish(&tcs
);
387 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
391 Return a pointer to the album name of the given slide_index
393 char* get_album_name(const int slide_index
)
395 return album_names
+ album
[slide_index
].name_idx
;
399 Return a pointer to the track name of the active album
400 create_track_index has to be called first.
402 char* get_track_name(const int track_index
)
404 if ( track_index
< track_count
)
405 return track_names
+ tracks
[track_index
].name_idx
;
410 Create the track index of the given slide_index.
412 int create_track_index(const int slide_index
)
414 if ( slide_index
== track_index
) {
418 if (!rb
->tagcache_search(&tcs
, tag_title
))
423 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
426 tracks
[0].name_idx
= 0;
428 while (rb
->tagcache_get_next(&tcs
) && track_count
< MAX_TRACKS
)
430 l
= rb
->strlen(tcs
.result
) + 1;
431 if ( track_count
> 0 )
432 tracks
[track_count
].name_idx
= tracks
[track_count
-1].name_idx
+ old_l
;
434 if ( (tracks
[track_count
].name_idx
+ l
) > MAX_TRACKS
* AVG_TRACK_NAME_LENGTH
)
436 /* not enough memory */
437 ret
= ERROR_BUFFER_FULL
;
440 rb
->strcpy(track_names
+ tracks
[track_count
].name_idx
, tcs
.result
);
441 tracks
[track_count
].seek
= tcs
.result_seek
;
446 rb
->tagcache_search_finish(&tcs
);
447 track_index
= slide_index
;
452 return (track_count
> 0) ? 0 : -1;
457 Determine filename of the album art for the given slide_index and
458 store the result in buf.
459 The algorithm looks for the first track of the given album uses
460 find_albumart to find the filename.
462 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
, int buflen
)
464 if ( slide_index
== -1 )
466 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
469 if (!rb
->tagcache_search(&tcs
, tag_filename
))
473 /* find the first track of the album */
474 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
476 if ( rb
->tagcache_get_next(&tcs
) ) {
479 rb
->snprintf(size
, sizeof(size
), ".%dx%d", PREFERRED_IMG_WIDTH
,
480 PREFERRED_IMG_HEIGHT
);
481 rb
->strncpy( (char*)&id3
.path
, tcs
.result
, MAX_PATH
);
482 id3
.album
= get_album_name(slide_index
);
483 if ( rb
->search_albumart_files(&id3
, size
, buf
, buflen
) )
485 else if ( rb
->search_albumart_files(&id3
, "", buf
, buflen
) )
491 /* did not find a matching track */
494 rb
->tagcache_search_finish(&tcs
);
499 Draw the PictureFlow logo
501 void draw_splashscreen(void)
503 struct screen
* display
= rb
->screens
[0];
505 rb
->lcd_set_background(LCD_RGBPACK(0,0,0));
506 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
507 rb
->lcd_clear_display();
509 const struct picture
* logo
= &(logos
[display
->screen_type
]);
510 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
517 Draw a simple progress bar
519 void draw_progressbar(int step
)
522 const int bar_height
= 22;
523 const int w
= LCD_WIDTH
- 20;
526 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
528 int y
= (LCD_HEIGHT
- txt_h
)/2;
530 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
533 rb
->lcd_set_foreground(LCD_RGBPACK(100,100,100));
534 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
535 rb
->lcd_set_foreground(LCD_RGBPACK(165, 231, 82));
537 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
538 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
544 Allocate temporary buffers
546 bool allocate_buffers(void)
548 int input_size
= MAX_IMG_WIDTH
* MAX_IMG_HEIGHT
* sizeof( fb_data
);
549 int output_size
= MAX_IMG_WIDTH
* MAX_IMG_HEIGHT
* sizeof( fb_data
) * 2;
551 input_hid
= rb
->bufalloc(NULL
, input_size
, TYPE_BITMAP
);
556 if (rb
->bufgetdata(input_hid
, 0, (void *)&input_bmp_buffer
) < input_size
) {
557 rb
->bufclose(input_hid
);
561 output_hid
= rb
->bufalloc(NULL
, output_size
, TYPE_BITMAP
);
563 if (output_hid
< 0) {
564 rb
->bufclose(input_hid
);
568 if (rb
->bufgetdata(output_hid
, 0, (void *)&output_bmp_buffer
) < output_size
) {
569 rb
->bufclose(output_hid
);
577 Free the temporary buffers
579 bool free_buffers(void)
581 rb
->bufclose(input_hid
);
582 rb
->bufclose(output_hid
);
587 Precomupte the album art images and store them in CACHE_PREFIX.
589 bool create_albumart_cache(bool force
)
591 number_of_slides
= album_count
;
594 if ( ! force
&& rb
->file_exists( CACHE_PREFIX
"/ready" ) ) return true;
597 struct bitmap input_bmp
;
599 config
.avg_album_width
= 0;
600 for (i
=0; i
< album_count
; i
++)
603 if (!get_albumart_for_index_from_db(i
, tmp_path_name
, MAX_PATH
))
606 input_bmp
.data
= (char *)input_bmp_buffer
;
607 ret
= rb
->read_bmp_file(tmp_path_name
, &input_bmp
,
608 sizeof(fb_data
)*MAX_IMG_WIDTH
*MAX_IMG_HEIGHT
,
611 rb
->splash(HZ
, "Could not read bmp");
612 continue; /* skip missing/broken files */
616 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw", i
);
617 if (!create_bmp(&input_bmp
, tmp_path_name
, false)) {
618 rb
->splash(HZ
, "Could not write bmp");
620 config
.avg_album_width
+= input_bmp
.width
;
622 if ( rb
->button_get(false) == PICTUREFLOW_MENU
) return false;
625 rb
->splash(2*HZ
, "No albums found");
628 config
.avg_album_width
/= slides
;
629 if ( config
.avg_album_width
== 0 ) {
630 rb
->splash(HZ
, "album size is 0");
633 fh
= rb
->creat( CACHE_PREFIX
"/ready" );
639 Return the index on the stack of slide_index.
640 Return -1 if slide_index is not on the stack.
642 static inline int slide_stack_get_index(const int slide_index
)
644 int i
= slide_cache_stack_index
+ 1;
646 if ( slide_cache_stack
[i
] == slide_index
) return i
;
652 Push the slide_index on the stack so the image will be loaded.
653 The algorithm tries to keep the center_index on top and the
654 slide_index as high as possible (so second if center_index is
657 void slide_stack_push(const int slide_index
)
659 rb
->mutex_lock(&slide_cache_stack_lock
);
661 if ( slide_cache_stack_index
== -1 ) {
662 /* empty stack, no checks at all */
663 slide_cache_stack
[ ++slide_cache_stack_index
] = slide_index
;
664 rb
->mutex_unlock(&slide_cache_stack_lock
);
668 int i
= slide_stack_get_index( slide_index
);
670 if ( i
== slide_cache_stack_index
) {
671 /* slide_index is on top, so we do not change anything */
672 rb
->mutex_unlock(&slide_cache_stack_lock
);
677 /* slide_index is already on the stack, but not on top */
678 int tmp
= slide_cache_stack
[ slide_cache_stack_index
];
679 if ( tmp
== center_index
) {
680 /* the center_index is on top of the stack so do not touch that */
681 if ( slide_cache_stack_index
> 0 ) {
682 /* but maybe it is possible to swap the given slide_index to the second place */
683 tmp
= slide_cache_stack
[ slide_cache_stack_index
-1 ];
684 slide_cache_stack
[ slide_cache_stack_index
- 1 ] = slide_cache_stack
[ i
];
685 slide_cache_stack
[ i
] = tmp
;
689 /* if the center_index is not on top (i.e. already loaded) bring the slide_index to the top */
690 slide_cache_stack
[ slide_cache_stack_index
] = slide_cache_stack
[ i
];
691 slide_cache_stack
[ i
] = tmp
;
695 /* slide_index is not on the stack */
696 if ( slide_cache_stack_index
>= SLIDE_CACHE_SIZE
-1 ) {
697 /* if we exceeded the stack size, clear the first half of the stack */
698 slide_cache_stack_index
= SLIDE_CACHE_SIZE
/2;
699 for (i
= 0; i
<= slide_cache_stack_index
; i
++)
700 slide_cache_stack
[ i
] = slide_cache_stack
[ i
+ slide_cache_stack_index
];
702 if ( slide_cache_stack
[ slide_cache_stack_index
] == center_index
) {
703 /* if the center_index is on top leave it there */
704 slide_cache_stack
[ slide_cache_stack_index
] = slide_index
;
705 slide_cache_stack
[ ++slide_cache_stack_index
] = center_index
;
708 /* usual stack case: push the slide_index on top */
709 slide_cache_stack
[ ++slide_cache_stack_index
] = slide_index
;
712 rb
->mutex_unlock(&slide_cache_stack_lock
);
717 Pop the topmost item from the stack and decrease the stack size
719 static inline int slide_stack_pop(void)
721 rb
->mutex_lock(&slide_cache_stack_lock
);
723 if ( slide_cache_stack_index
>= 0 )
724 result
= slide_cache_stack
[ slide_cache_stack_index
-- ];
727 rb
->mutex_unlock(&slide_cache_stack_lock
);
733 Load the slide into the cache.
734 Thus we have to queue the loading request in our thread while discarding the
737 static inline void request_surface(const int slide_index
)
739 slide_stack_push(slide_index
);
740 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
745 Thread used for loading and preparing bitmaps in the background
749 long sleep_time
= 5 * HZ
;
750 struct queue_event ev
;
752 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
757 /* we just woke up */
761 while ( (slide_index
= slide_stack_pop()) != -1 ) {
762 load_surface( slide_index
);
763 rb
->queue_wait_w_tmo(&thread_q
, &ev
, HZ
/10);
774 End the thread by posting the EV_EXIT event
776 void end_pf_thread(void)
778 if ( thread_is_running
) {
779 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
780 rb
->thread_wait(thread_id
);
781 /* remove the thread's queue from the broadcast list */
782 rb
->queue_delete(&thread_q
);
783 thread_is_running
= false;
790 Create the thread an setup the event queue
792 bool create_pf_thread(void)
794 rb
->queue_init(&thread_q
, true); /* put the thread's queue in the bcast list */
795 if ((thread_id
= rb
->create_thread(
798 sizeof(thread_stack
),
800 "Picture load thread"
801 IF_PRIO(, PRIORITY_BACKGROUND
)
807 thread_is_running
= true;
812 Safe the given bitmap as filename in the pfraw format
814 bool save_pfraw(char* filename
, struct bitmap
*bm
)
816 struct pfraw_header bmph
;
817 bmph
.width
= bm
->width
;
818 bmph
.height
= bm
->height
;
819 int fh
= rb
->creat( filename
);
820 if( fh
< 0 ) return false;
821 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
823 for( y
= 0; y
< bm
->height
; y
++ )
825 fb_data
*d
= (fb_data
*)( bm
->data
) + (y
*bm
->width
);
826 rb
->write( fh
, d
, sizeof( fb_data
) * bm
->width
);
834 Read the pfraw image given as filename and return the hid of the buffer
836 int read_pfraw(char* filename
)
838 struct pfraw_header bmph
;
839 int fh
= rb
->open(filename
, O_RDONLY
);
840 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
842 return empty_slide_hid
;
845 int size
= sizeof(struct bitmap
) + sizeof( fb_data
) * bmph
.width
* bmph
.height
;
847 int hid
= rb
->bufalloc(NULL
, size
, TYPE_BITMAP
);
854 if (rb
->bufgetdata(hid
, 0, (void *)&bm
) < size
) {
859 bm
->width
= bmph
.width
;
860 bm
->height
= bmph
.height
;
861 bm
->format
= FORMAT_NATIVE
;
862 bm
->data
= ((unsigned char *)bm
+ sizeof(struct bitmap
));
865 for( y
= 0; y
< bm
->height
; y
++ )
867 fb_data
*d
= (fb_data
*)( bm
->data
) + (y
*bm
->width
);
868 rb
->read( fh
, d
, sizeof( fb_data
) * bm
->width
);
876 Create the slide with its reflection for the given slide_index and filename
877 and store it as pfraw in CACHE_PREFIX/[slide_index].pfraw
879 bool create_bmp(struct bitmap
*input_bmp
, char *target_path
, bool resize
)
881 struct bitmap output_bmp
;
883 output_bmp
.format
= input_bmp
->format
;
884 output_bmp
.data
= (char *)output_bmp_buffer
;
888 output_bmp
.width
= config
.avg_album_width
;
889 output_bmp
.height
= config
.avg_album_width
;
890 simple_resize_bitmap(input_bmp
, &output_bmp
);
892 /* Resized bitmap is now in the output buffer,
893 copy it back to the input buffer */
894 rb
->memcpy(input_bmp_buffer
, output_bmp_buffer
,
895 config
.avg_album_width
* config
.avg_album_width
* sizeof(fb_data
));
896 input_bmp
->data
= (char *)input_bmp_buffer
;
897 input_bmp
->width
= output_bmp
.width
;
898 input_bmp
->height
= output_bmp
.height
;
901 output_bmp
.width
= input_bmp
->width
* 2;
902 output_bmp
.height
= input_bmp
->height
;
904 fb_data
*src
= (fb_data
*)input_bmp
->data
;
905 fb_data
*dst
= (fb_data
*)output_bmp
.data
;
907 /* transpose the image, this is to speed-up the rendering
908 because we process one column at a time
909 (and much better and faster to work row-wise, i.e in one scanline) */
910 int hofs
= input_bmp
->width
/ 3;
911 rb
->memset(dst
, 0, sizeof(fb_data
) * output_bmp
.width
* output_bmp
.height
);
913 for (x
= 0; x
< input_bmp
->width
; x
++)
914 for (y
= 0; y
< input_bmp
->height
; y
++)
915 dst
[output_bmp
.width
* x
+ (hofs
+ y
)] =
916 src
[y
* input_bmp
->width
+ x
];
918 /* create the reflection */
919 int ht
= input_bmp
->height
- hofs
;
921 for (x
= 0; x
< input_bmp
->width
; x
++) {
922 for (y
= 0; y
< ht
; y
++) {
923 fb_data color
= src
[x
+ input_bmp
->width
* (input_bmp
->height
- y
- 1)];
924 int r
= RGB_UNPACK_RED(color
) * (hte
- y
) / hte
* 3 / 5;
925 int g
= RGB_UNPACK_GREEN(color
) * (hte
- y
) / hte
* 3 / 5;
926 int b
= RGB_UNPACK_BLUE(color
) * (hte
- y
) / hte
* 3 / 5;
927 dst
[output_bmp
.height
+ hofs
+ y
+ output_bmp
.width
* x
] =
928 LCD_RGBPACK(r
, g
, b
);
931 return save_pfraw(target_path
, &output_bmp
);
936 Load the surface for the given slide_index into the cache at cache_index.
938 static inline bool load_and_prepare_surface(const int slide_index
,
939 const int cache_index
)
941 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
944 int hid
= read_pfraw(tmp_path_name
);
948 cache
[cache_index
].hid
= hid
;
950 if ( cache_index
< SLIDE_CACHE_SIZE
) {
951 cache
[cache_index
].index
= slide_index
;
952 cache
[cache_index
].touched
= *rb
->current_tick
;
960 Load the surface from a bmp and overwrite the oldest slide in the cache
963 int load_surface(const int slide_index
)
965 long oldest_tick
= *rb
->current_tick
;
966 int oldest_slide
= 0;
968 if ( slide_cache_in_use
< SLIDE_CACHE_SIZE
) { /* initial fill */
969 oldest_slide
= slide_cache_in_use
;
970 load_and_prepare_surface(slide_index
, slide_cache_in_use
++);
973 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) { /* look for oldest slide */
974 if (cache
[i
].touched
< oldest_tick
) {
976 oldest_tick
= cache
[i
].touched
;
979 if (cache
[oldest_slide
].hid
!= empty_slide_hid
) {
980 rb
->bufclose(cache
[oldest_slide
].hid
);
981 cache
[oldest_slide
].hid
= -1;
983 load_and_prepare_surface(slide_index
, oldest_slide
);
990 Get a slide from the buffer
992 static inline struct bitmap
*get_slide(const int hid
)
999 ssize_t ret
= rb
->bufgetdata(hid
, 0, (void *)&bmp
);
1008 Return the requested surface
1010 static inline struct bitmap
*surface(const int slide_index
)
1012 if (slide_index
< 0)
1014 if (slide_index
>= number_of_slides
)
1018 for (i
= 0; i
< slide_cache_in_use
; i
++) { /* maybe do the inverse mapping => implies dynamic allocation? */
1019 if ( cache
[i
].index
== slide_index
) {
1020 /* We have already loaded our slide, so touch it and return it. */
1021 cache
[i
].touched
= *rb
->current_tick
;
1022 return get_slide(cache
[i
].hid
);
1025 request_surface(slide_index
);
1026 return get_slide(empty_slide_hid
);
1030 adjust slides so that they are in "steady state" position
1032 void reset_slides(void)
1034 center_slide
.angle
= 0;
1035 center_slide
.cx
= 0;
1036 center_slide
.cy
= 0;
1037 center_slide
.distance
= 0;
1038 center_slide
.slide_index
= center_index
;
1041 for (i
= 0; i
< config
.show_slides
; i
++) {
1042 struct slide_data
*si
= &left_slides
[i
];
1044 si
->cx
= -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
);
1046 si
->slide_index
= center_index
- 1 - i
;
1050 for (i
= 0; i
< config
.show_slides
; i
++) {
1051 struct slide_data
*si
= &right_slides
[i
];
1053 si
->cx
= offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
;
1055 si
->slide_index
= center_index
+ 1 + i
;
1062 Updates look-up table and other stuff necessary for the rendering.
1063 Call this when the viewport size or slide dimension is changed.
1065 void recalc_table(void)
1067 int w
= (BUFFER_WIDTH
+ 1) / 2;
1068 int h
= (BUFFER_HEIGHT
+ 1) / 2;
1070 for (i
= 0; i
< w
; i
++) {
1071 PFreal gg
= (PFREAL_HALF
+ i
* PFREAL_ONE
) / (2 * h
);
1072 rays
[w
- i
- 1] = -gg
;
1076 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1078 offsetX
= config
.avg_album_width
/ 2 * (PFREAL_ONE
- fcos(itilt
));
1079 offsetY
= config
.avg_album_width
/ 2 * fsin(itilt
);
1080 offsetX
+= config
.avg_album_width
* PFREAL_ONE
;
1081 offsetY
+= config
.avg_album_width
* PFREAL_ONE
/ 4;
1082 offsetX
+= config
.extra_spacing_for_center_slide
<< PFREAL_SHIFT
;
1087 Fade the given color by spreading the fb_data (ushort)
1088 to an uint, multiply and compress the result back to a ushort.
1090 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1091 static inline fb_data
fade_color(fb_data c
, unsigned int a
)
1094 unsigned int p
= ((((c
|(c
<<16)) & 0x07e0f81f) * a
) >> 5) & 0x07e0f81f;
1095 return swap16( (fb_data
) (p
| ( p
>> 16 )) );
1098 static inline fb_data
fade_color(fb_data c
, unsigned int a
)
1100 unsigned int p
= ((((c
|(c
<<16)) & 0x07e0f81f) * a
) >> 5) & 0x07e0f81f;
1101 return (p
| ( p
>> 16 ));
1109 Render a single slide
1111 void render_slide(struct slide_data
*slide
, struct rect
*result_rect
,
1112 const int alpha
, int col1
, int col2
)
1114 rb
->memset(result_rect
, 0, sizeof(struct rect
));
1115 struct bitmap
*bmp
= surface(slide
->slide_index
);
1119 fb_data
*src
= (fb_data
*)bmp
->data
;
1121 const int sw
= bmp
->height
;
1122 const int sh
= bmp
->width
;
1124 const int h
= LCD_HEIGHT
;
1125 const int w
= LCD_WIDTH
;
1133 col1
= (col1
>= 0) ? col1
: 0;
1134 col2
= (col2
>= 0) ? col2
: w
- 1;
1135 col1
= fmin(col1
, w
- 1);
1136 col2
= fmin(col2
, w
- 1);
1138 int distance
= (h
+ slide
->distance
) * 100 / config
.zoom
;
1139 if (distance
< 100 ) distance
= 100; /* clamp distances */
1140 PFreal sdx
= fcos(slide
->angle
);
1141 PFreal sdy
= fsin(slide
->angle
);
1142 PFreal xs
= slide
->cx
- bmp
->width
* sdx
/ 4;
1143 PFreal ys
= slide
->cy
- bmp
->width
* sdy
/ 4;
1144 PFreal dist
= distance
* PFREAL_ONE
;
1146 const int alpha4
= alpha
>> 3;
1148 int xi
= fmax((PFreal
) 0,
1149 ((w
* PFREAL_ONE
/ 2) +
1150 fdiv(xs
* h
, dist
+ ys
)) >> PFREAL_SHIFT
);
1156 result_rect
->left
= xi
;
1158 for (x
= fmax(xi
, col1
); x
<= col2
; x
++) {
1160 PFreal fk
= rays
[x
];
1162 fk
= fk
- fdiv(sdx
, sdy
);
1163 hity
= -fdiv(( rays
[x
] * distance
1165 + slide
->cy
* sdx
/ sdy
), fk
);
1168 dist
= distance
* PFREAL_ONE
+ hity
;
1172 PFreal hitx
= fmul(dist
, rays
[x
]);
1174 PFreal hitdist
= fdiv(hitx
- slide
->cx
, sdx
);
1176 const int column
= (sw
>> 1) + (hitdist
>> PFREAL_SHIFT
);
1183 result_rect
->right
= x
;
1185 result_rect
->left
= x
;
1190 fb_data
*pixel1
= &buffer
[y1
* BUFFER_WIDTH
+ x
];
1191 fb_data
*pixel2
= &buffer
[y2
* BUFFER_WIDTH
+ x
];
1192 const int pixelstep
= pixel2
- pixel1
;
1194 int center
= (sh
>> 1);
1196 int p1
= center
* PFREAL_ONE
- (dy
>> 2);
1197 int p2
= center
* PFREAL_ONE
+ (dy
>> 2);
1199 const fb_data
*ptr
= &src
[column
* bmp
->width
];
1202 while ((y1
>= 0) && (y2
< h
) && (p1
>= 0)) {
1203 *pixel1
= ptr
[p1
>> PFREAL_SHIFT
];
1204 *pixel2
= ptr
[p2
>> PFREAL_SHIFT
];
1209 pixel1
-= pixelstep
;
1210 pixel2
+= pixelstep
;
1212 while ((y1
>= 0) && (y2
< h
) && (p1
>= 0)) {
1213 *pixel1
= fade_color(ptr
[p1
>> PFREAL_SHIFT
], alpha4
);
1214 *pixel2
= fade_color(ptr
[p2
>> PFREAL_SHIFT
], alpha4
);
1219 pixel1
-= pixelstep
;
1220 pixel2
+= pixelstep
;
1223 /* let the music play... */
1226 result_rect
->top
= 0;
1227 result_rect
->bottom
= h
- 1;
1233 Jump the the given slide_index
1235 static inline void set_current_slide(const int slide_index
)
1238 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1239 target
= center_index
;
1240 slide_frame
= slide_index
<< 16;
1245 Start the animation for changing slides
1247 void start_animation(void)
1249 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1250 pf_state
= pf_scrolling
;
1254 Go to the previous slide
1256 void show_previous_slide(void)
1259 if (center_index
> 0) {
1260 target
= center_index
- 1;
1263 } else if ( step
> 0 ) {
1264 target
= center_index
;
1267 target
= fmax(0, center_index
- 2);
1273 Go to the next slide
1275 void show_next_slide(void)
1278 if (center_index
< number_of_slides
- 1) {
1279 target
= center_index
+ 1;
1282 } else if ( step
< 0 ) {
1283 target
= center_index
;
1286 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1292 Return true if the rect has size 0
1294 static inline bool is_empty_rect(struct rect
*r
)
1296 return ((r
->left
== 0) && (r
->right
== 0) && (r
->top
== 0)
1297 && (r
->bottom
== 0));
1302 Render the slides. Updates only the offscreen buffer.
1304 void render_all_slides(void)
1306 rb
->lcd_set_background(LCD_RGBPACK(0,0,0));
1307 rb
->lcd_clear_display(); /* TODO: Optimizes this by e.g. invalidating rects */
1309 int nleft
= config
.show_slides
;
1310 int nright
= config
.show_slides
;
1313 r
.left
= LCD_WIDTH
; r
.top
= 0; r
.bottom
= 0; r
.right
= 0;
1314 render_slide(¢er_slide
, &r
, 256, -1, -1);
1316 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1322 /* no animation, boring plain rendering */
1323 for (index
= 0; index
< nleft
- 1; index
++) {
1324 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1325 alpha
-= extra_fade
;
1326 if (alpha
< 0 ) alpha
= 0;
1327 render_slide(&left_slides
[index
], &r
, alpha
, 0, c1
- 1);
1328 if (!is_empty_rect(&r
)) {
1330 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1335 for (index
= 0; index
< nright
- 1; index
++) {
1336 int alpha
= (index
< nright
- 2) ? 256 : 128;
1337 alpha
-= extra_fade
;
1338 if (alpha
< 0 ) alpha
= 0;
1339 render_slide(&right_slides
[index
], &r
, alpha
, c2
+ 1,
1341 if (!is_empty_rect(&r
)) {
1343 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1349 if ( step
< 0 ) c1
= BUFFER_WIDTH
;
1350 /* the first and last slide must fade in/fade out */
1351 for (index
= 0; index
< nleft
; index
++) {
1353 if (index
== nleft
- 1)
1354 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1355 if (index
== nleft
- 2)
1356 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1357 if (index
== nleft
- 3)
1358 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1359 render_slide(&left_slides
[index
], &r
, alpha
, 0, c1
- 1);
1361 if (!is_empty_rect(&r
)) {
1363 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1368 if ( step
> 0 ) c2
= 0;
1369 for (index
= 0; index
< nright
; index
++) {
1370 int alpha
= (index
< nright
- 2) ? 256 : 128;
1371 if (index
== nright
- 1)
1372 alpha
= (step
> 0) ? fade
/ 2 : 0;
1373 if (index
== nright
- 2)
1374 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1375 if (index
== nright
- 3)
1376 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1377 render_slide(&right_slides
[index
], &r
, alpha
, c2
+ 1,
1379 if (!is_empty_rect(&r
)) {
1381 rb
->lcd_drawrect(r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
);
1391 Updates the animation effect. Call this periodically from a timer.
1393 void update_scroll_animation(void)
1401 /* deaccelerate when approaching the target */
1403 const int max
= 2 * 65536;
1405 int fi
= slide_frame
;
1406 fi
-= (target
<< 16);
1411 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1412 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1415 slide_frame
+= speed
* step
;
1417 int index
= slide_frame
>> 16;
1418 int pos
= slide_frame
& 0xffff;
1419 int neg
= 65536 - pos
;
1420 int tick
= (step
< 0) ? neg
: pos
;
1421 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1423 /* the leftmost and rightmost slide must fade away */
1428 if (center_index
!= index
) {
1429 center_index
= index
;
1430 slide_frame
= index
<< 16;
1431 center_slide
.slide_index
= center_index
;
1432 for (i
= 0; i
< config
.show_slides
; i
++)
1433 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1434 for (i
= 0; i
< config
.show_slides
; i
++)
1435 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1438 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1439 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1440 center_slide
.cy
= fmul(offsetY
, ftick
);
1442 if (center_index
== target
) {
1450 for (i
= 0; i
< config
.show_slides
; i
++) {
1451 struct slide_data
*si
= &left_slides
[i
];
1454 -(offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
+ step
1455 * config
.spacing_between_slides
* ftick
);
1459 for (i
= 0; i
< config
.show_slides
; i
++) {
1460 struct slide_data
*si
= &right_slides
[i
];
1463 offsetX
+ config
.spacing_between_slides
* i
* PFREAL_ONE
- step
1464 * config
.spacing_between_slides
* ftick
;
1469 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1470 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1471 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1472 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1474 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1475 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1476 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1477 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1480 /* must change direction ? */
1493 void cleanup(void *parameter
)
1496 /* Turn on backlight timeout (revert to settings) */
1497 backlight_use_settings(rb
); /* backlight control in lib/helper.c */
1500 for (i
= 0; i
< slide_cache_in_use
; i
++) {
1501 rb
->bufclose(cache
[i
].hid
);
1503 if ( empty_slide_hid
!= - 1)
1504 rb
->bufclose(empty_slide_hid
);
1505 rb
->lcd_set_drawmode(old_drawmode
);
1509 Create the "?" slide, that is shown while loading
1510 or when no cover was found.
1512 int create_empty_slide(bool force
)
1514 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1515 struct bitmap input_bmp
;
1516 input_bmp
.width
= BMPWIDTH_pictureflow_emptyslide
;
1517 input_bmp
.height
= BMPHEIGHT_pictureflow_emptyslide
;
1518 input_bmp
.format
= FORMAT_NATIVE
;
1519 input_bmp
.data
= (char*) &pictureflow_emptyslide
;
1520 if ( ! create_bmp(&input_bmp
, EMPTY_SLIDE
, true) ) return false;
1523 empty_slide_hid
= read_pfraw( EMPTY_SLIDE
);
1524 if (empty_slide_hid
== -1 ) return false;
1531 Shows the settings menu
1533 int settings_menu(void) {
1536 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1537 "Spacing", "Center margin", "Number of slides", "Zoom",
1541 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1544 rb
->set_bool("Show FPS", &show_fps
);
1548 rb
->set_int("Spacing between slides", "", 1,
1549 &(config
.spacing_between_slides
),
1550 NULL
, 1, 0, 100, NULL
);
1556 rb
->set_int("Center margin", "", 1,
1557 &(config
.extra_spacing_for_center_slide
),
1558 NULL
, 1, -50, 50, NULL
);
1564 rb
->set_int("Number of slides", "", 1, &(config
.show_slides
),
1565 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
1571 rb
->set_int("Number of slides", "", 1, &(config
.zoom
),
1572 NULL
, 1, 10, 300, NULL
);
1578 rb
->remove(CACHE_PREFIX
"/ready");
1579 rb
->remove(EMPTY_SLIDE
);
1580 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
1583 case MENU_ATTACHED_USB
:
1584 return PLUGIN_USB_CONNECTED
;
1586 } while ( selection
>= 0 );
1598 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1600 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
1601 "Settings", "Return", "Quit");
1604 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
1606 result
= settings_menu();
1607 if ( result
!= 0 ) return result
;
1616 case MENU_ATTACHED_USB
:
1617 return PLUGIN_USB_CONNECTED
;
1626 Fill the config struct with some defaults
1628 void set_default_config(void)
1630 config
.spacing_between_slides
= 40;
1631 config
.extra_spacing_for_center_slide
= 0;
1632 config
.show_slides
= 3;
1633 config
.avg_album_width
= 0;
1638 Read the config file.
1639 For now, the size has to match.
1640 Later a version number might be appropiate.
1642 bool read_pfconfig(void)
1644 set_default_config();
1646 int fh
= rb
->open( CONFIG_FILE
, O_RDONLY
);
1647 if ( fh
< 0 ) { /* no config yet */
1650 int ret
= rb
->read(fh
, &config
, sizeof(struct config_data
));
1652 if ( ret
!= sizeof(struct config_data
) ) {
1653 set_default_config();
1654 rb
->splash(2*HZ
, "Config invalid. Using defaults");
1660 Write the config file
1662 bool write_pfconfig(void)
1664 int fh
= rb
->creat( CONFIG_FILE
);
1665 if( fh
< 0 ) return false;
1666 rb
->write( fh
, &config
, sizeof( struct config_data
) );
1672 Animation step for zooming into the current cover
1674 void update_cover_in_animation(void)
1676 cover_animation_keyframe
++;
1677 if( cover_animation_keyframe
< 20 ) {
1678 center_slide
.distance
-=5;
1679 center_slide
.angle
+=1;
1682 else if( cover_animation_keyframe
< 35 ) {
1683 center_slide
.angle
+=16;
1686 cover_animation_keyframe
= 0;
1688 pf_state
= pf_show_tracks
;
1693 Animation step for zooming out the current cover
1695 void update_cover_out_animation(void)
1697 cover_animation_keyframe
++;
1698 if( cover_animation_keyframe
<= 15 ) {
1699 center_slide
.angle
-=16;
1701 else if( cover_animation_keyframe
< 35 ) {
1702 center_slide
.distance
+=5;
1703 center_slide
.angle
-=1;
1707 cover_animation_keyframe
= 0;
1713 Draw a blue gradient at y with height h
1715 static inline void draw_gradient(int y
, int h
)
1717 static int r
, inc
, c
;
1718 inc
= (100 << 8) / h
;
1720 selected_track_pulse
= (selected_track_pulse
+1) % 10;
1721 int c2
= selected_track_pulse
- 5;
1722 for (r
=0; r
<h
; r
++) {
1723 rb
->lcd_set_foreground(LCD_RGBPACK(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
1725 rb
->lcd_hline(0, LCD_WIDTH
, r
+y
);
1735 Reset the track list after a album change
1737 void reset_track_list(void)
1739 int albumtxt_w
, albumtxt_h
;
1740 const char* albumtxt
= get_album_name(center_index
);
1741 rb
->lcd_getstringsize(albumtxt
, &albumtxt_w
, &albumtxt_h
);
1742 const int height
= LCD_HEIGHT
-albumtxt_h
-10;
1743 track_list_visible_entries
= fmin( height
/albumtxt_h
, track_count
);
1744 start_index_track_list
= 0;
1745 track_scroll_index
= 0;
1746 track_scroll_dir
= 1;
1751 Display the list of tracks
1753 void show_track_list(void)
1755 rb
->lcd_clear_display();
1756 if ( center_slide
.slide_index
!= track_index
) {
1757 create_track_index(center_slide
.slide_index
);
1760 static int titletxt_w
, titletxt_h
, titletxt_y
, titletxt_x
, i
, color
;
1763 for (i
=0; i
< track_list_visible_entries
; i
++) {
1764 track_i
= i
+start_index_track_list
;
1765 rb
->lcd_getstringsize(get_track_name(track_i
), &titletxt_w
, &titletxt_h
);
1766 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
1767 if ( track_i
== selected_track
) {
1768 draw_gradient(titletxt_y
, titletxt_h
);
1769 rb
->lcd_set_foreground(LCD_RGBPACK(255,255,255));
1770 if (titletxt_w
> LCD_WIDTH
) {
1771 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
1772 track_scroll_dir
= 1;
1773 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
1774 track_scroll_index
+= track_scroll_dir
*2;
1775 titletxt_x
= track_scroll_index
;
1777 rb
->lcd_putsxy(titletxt_x
,titletxt_y
,get_track_name(track_i
));
1780 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
1781 rb
->lcd_set_foreground(LCD_RGBPACK(color
,color
,color
));
1782 rb
->lcd_putsxy(titletxt_x
,titletxt_y
,get_track_name(track_i
));
1784 titletxt_y
+= titletxt_h
;
1788 void select_next_track(void)
1790 if ( selected_track
< track_count
- 1 ) {
1792 track_scroll_index
= 0;
1793 track_scroll_dir
= 1;
1794 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
1795 start_index_track_list
++;
1799 void select_prev_track(void)
1801 if (selected_track
> 0 ) {
1802 if (selected_track
==start_index_track_list
) start_index_track_list
--;
1803 track_scroll_index
= 0;
1804 track_scroll_dir
= 1;
1810 Draw the current album name
1812 void draw_album_text(void)
1814 int albumtxt_w
, albumtxt_h
;
1819 /* Draw album text */
1820 if ( pf_state
== pf_scrolling
) {
1821 c
= ((slide_frame
& 0xffff )/ 255);
1822 if (step
< 0) c
= 255-c
;
1823 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
1824 albumtxt
= get_album_name(center_index
+step
);
1828 albumtxt
= get_album_name(center_index
);
1834 albumtxt
= get_album_name(center_index
);
1837 rb
->lcd_set_foreground(LCD_RGBPACK(c
,c
,c
));
1838 rb
->lcd_getstringsize(albumtxt
, &albumtxt_w
, &albumtxt_h
);
1839 if (center_index
!= prev_center_index
) {
1842 prev_center_index
= center_index
;
1844 albumtxt_y
= LCD_HEIGHT
-albumtxt_h
-10;
1846 if (albumtxt_w
> LCD_WIDTH
) {
1847 rb
->lcd_putsxy(albumtxt_x
, albumtxt_y
, albumtxt
);
1848 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
1849 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
1850 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
1851 albumtxt_x
+= albumtxt_dir
;
1855 rb
->lcd_putsxy((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
1863 Main function that also contain the main plasma
1869 draw_splashscreen();
1871 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
1872 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
1873 rb
->splash(HZ
, "Could not create directory " CACHE_PREFIX
);
1874 return PLUGIN_ERROR
;
1878 if (!read_pfconfig()) {
1879 rb
->splash(HZ
, "Error in config. Please delete " CONFIG_FILE
);
1880 return PLUGIN_ERROR
;
1883 if (!allocate_buffers()) {
1884 rb
->splash(HZ
, "Could not allocate temporary buffers");
1885 return PLUGIN_ERROR
;
1888 ret
= create_album_index();
1889 if (ret
== ERROR_BUFFER_FULL
) {
1890 rb
->splash(HZ
, "Not enough memory for album names");
1891 return PLUGIN_ERROR
;
1892 } else if (ret
== ERROR_NO_ALBUMS
) {
1893 rb
->splash(HZ
, "No albums found. Please enable database");
1894 return PLUGIN_ERROR
;
1897 if (!create_albumart_cache(config
.avg_album_width
== 0)) {
1898 rb
->splash(HZ
, "Could not create album art cache");
1899 return PLUGIN_ERROR
;
1902 if (!create_empty_slide(false)) {
1903 rb
->splash(HZ
, "Could not load the empty slide");
1904 return PLUGIN_ERROR
;
1907 if (!free_buffers()) {
1908 rb
->splash(HZ
, "Could not free temporary buffers");
1909 return PLUGIN_ERROR
;
1912 if (!create_pf_thread()) {
1913 rb
->splash(HZ
, "Cannot create thread!");
1914 return PLUGIN_ERROR
;
1920 int min_slide_cache
= fmin(number_of_slides
, SLIDE_CACHE_SIZE
);
1921 for (i
= 0; i
< min_slide_cache
; i
++) {
1923 cache
[i
].touched
= 0;
1924 slide_cache_stack
[i
] = SLIDE_CACHE_SIZE
-i
-1;
1926 slide_cache_stack_index
= min_slide_cache
-1;
1927 slide_cache_in_use
= 0;
1928 buffer
= rb
->lcd_framebuffer
;
1948 long last_update
= *rb
->current_tick
;
1949 long current_update
;
1950 long update_interval
= 100;
1953 bool instant_update
;
1954 old_drawmode
= rb
->lcd_get_drawmode();
1955 rb
->lcd_set_drawmode(DRMODE_FG
);
1957 current_update
= *rb
->current_tick
;
1960 /* Initial rendering */
1961 instant_update
= false;
1964 switch ( pf_state
) {
1966 update_scroll_animation();
1967 render_all_slides();
1968 instant_update
= true;
1971 update_cover_in_animation();
1972 render_all_slides();
1973 instant_update
= true;
1976 update_cover_out_animation();
1977 render_all_slides();
1978 instant_update
= true;
1980 case pf_show_tracks
:
1984 render_all_slides();
1989 if (current_update
- last_update
> update_interval
) {
1990 fps
= frames
* HZ
/ (current_update
- last_update
);
1991 last_update
= current_update
;
1997 rb
->lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
1998 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
1999 rb
->lcd_putsxy(0, 0, fpstxt
);
2005 /* Copy offscreen buffer to LCD and give time to other threads */
2009 /*/ Handle buttons */
2010 button
= pluginlib_getaction(rb
, instant_update
? 0 : HZ
/16,
2011 plugin_contexts
, NB_ACTION_CONTEXTS
);
2014 case PICTUREFLOW_QUIT
:
2017 case PICTUREFLOW_MENU
:
2018 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
) {
2020 if ( ret
== -1 ) return PLUGIN_OK
;
2021 if ( ret
!= 0 ) return i
;
2022 rb
->lcd_set_drawmode(DRMODE_FG
);
2025 pf_state
= pf_cover_out
;
2029 case PICTUREFLOW_NEXT_ALBUM
:
2030 case PICTUREFLOW_NEXT_ALBUM_REPEAT
:
2032 if ( pf_state
== pf_show_tracks
)
2033 select_next_track();
2035 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2039 case PICTUREFLOW_PREV_ALBUM
:
2040 case PICTUREFLOW_PREV_ALBUM_REPEAT
:
2042 if ( pf_state
== pf_show_tracks
)
2043 select_prev_track();
2045 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2046 show_previous_slide();
2050 case PICTUREFLOW_NEXT_TRACK
:
2051 case PICTUREFLOW_NEXT_TRACK_REPEAT
:
2052 if ( pf_state
== pf_show_tracks
)
2053 select_next_track();
2056 case PICTUREFLOW_PREV_TRACK
:
2057 case PICTUREFLOW_PREV_TRACK_REPEAT
:
2058 if ( pf_state
== pf_show_tracks
)
2059 select_prev_track();
2063 case PICTUREFLOW_SELECT_ALBUM
:
2064 if ( pf_state
== pf_idle
)
2065 pf_state
= pf_cover_in
;
2066 if ( pf_state
== pf_show_tracks
)
2067 pf_state
= pf_cover_out
;
2071 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2072 == SYS_USB_CONNECTED
)
2073 return PLUGIN_USB_CONNECTED
;
2081 /*************************** Plugin entry point ****************************/
2083 enum plugin_status
plugin_start(const struct plugin_api
*api
, const void *parameter
)
2087 rb
= api
; /* copy to global api pointer */
2090 rb
->lcd_set_backdrop(NULL
);
2092 /* Turn off backlight timeout */
2093 backlight_force_on(rb
); /* backlight control in lib/helper.c */
2094 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2095 rb
->cpu_boost(true);
2098 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2099 rb
->cpu_boost(false);
2101 if ( ret
== PLUGIN_OK
) {
2102 if (!write_pfconfig()) {
2103 rb
->splash(HZ
, "Error writing config.");