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 ****************************************************************************/
28 #include "lib/pluginlib_actions.h"
29 #include "lib/helper.h"
30 #include "lib/configfile.h"
31 #include "lib/picture.h"
32 #include "pluginbitmaps/pictureflow_logo.h"
34 #include "lib/feature_wrappers.h"
35 #include "lib/buflib.h"
39 /******************************* Globals ***********************************/
41 const struct button_mapping
*plugin_contexts
[]
42 = {generic_actions
, generic_directions
};
44 #define NB_ACTION_CONTEXTS sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
48 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
49 #else /* LCD_DEPTH <= 1 */
50 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
51 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
52 #define PICTUREFLOW_DRMODE DRMODE_SOLID
54 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
56 #endif /* LCD_DEPTH <= 1 */
59 #define LCD_BUF _grey_info.buffer
60 #define MYLCD(fn) grey_ ## fn
61 #define G_PIX(r,g,b) \
62 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
63 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
64 #define G_BRIGHT(y) (y)
65 #define BUFFER_WIDTH _grey_info.width
66 #define BUFFER_HEIGHT _grey_info.height
67 typedef unsigned char pix_t
;
68 #else /* LCD_DEPTH >= 8 */
69 #define LCD_BUF rb->lcd_framebuffer
70 #define MYLCD(fn) rb->lcd_ ## fn
71 #define G_PIX LCD_RGBPACK
72 #define N_PIX LCD_RGBPACK
73 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
74 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
75 #define BUFFER_WIDTH LCD_WIDTH
76 #define BUFFER_HEIGHT LCD_HEIGHT
77 typedef fb_data pix_t
;
78 #endif /* LCD_DEPTH >= 8 */
80 #ifdef HAVE_SCROLLWHEEL
81 #define PICTUREFLOW_NEXT_ALBUM PLA_DOWN
82 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_DOWN_REPEAT
83 #define PICTUREFLOW_PREV_ALBUM PLA_UP
84 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_UP_REPEAT
86 #define PICTUREFLOW_NEXT_ALBUM PLA_RIGHT
87 #define PICTUREFLOW_NEXT_ALBUM_REPEAT PLA_RIGHT_REPEAT
88 #define PICTUREFLOW_PREV_ALBUM PLA_LEFT
89 #define PICTUREFLOW_PREV_ALBUM_REPEAT PLA_LEFT_REPEAT
90 #define PICTUREFLOW_NEXT_TRACK PLA_DOWN
91 #define PICTUREFLOW_NEXT_TRACK_REPEAT PLA_DOWN_REPEAT
92 #define PICTUREFLOW_PREV_TRACK PLA_UP
93 #define PICTUREFLOW_PREV_TRACK_REPEAT PLA_UP_REPEAT
95 #define PICTUREFLOW_MENU PLA_MENU
96 #define PICTUREFLOW_QUIT PLA_QUIT
97 #define PICTUREFLOW_SELECT_ALBUM PLA_FIRE
100 /* for fixed-point arithmetic, we need minimum 32-bit long
101 long long (64-bit) might be useful for multiplication and division */
103 #define PFREAL_SHIFT 10
104 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
105 #define PFREAL_ONE (1 << PFREAL_SHIFT)
106 #define PFREAL_HALF (PFREAL_ONE >> 1)
109 #define IANGLE_MAX 1024
110 #define IANGLE_MASK 1023
112 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
113 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
114 #define DISPLAY_HEIGHT REFLECT_TOP
115 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
116 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
117 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
118 (REFLECT_HEIGHT * 5))
119 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
120 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
121 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
122 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
123 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
125 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
127 #define MAX_SLIDES_COUNT 10
129 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
130 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
133 #define EV_WAKEUP 1337
135 /* maximum number of albums */
137 #define MAX_TRACKS 50
138 #define AVG_TRACK_NAME_LENGTH 20
141 #define UNIQBUF_SIZE (64*1024)
143 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
144 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
146 /* Error return values */
147 #define ERROR_NO_ALBUMS -1
148 #define ERROR_BUFFER_FULL -2
150 /* current version for cover cache */
151 #define CACHE_VERSION 2
152 #define CONFIG_VERSION 1
153 #define CONFIG_FILE "pictureflow.cfg"
155 /** structs we use */
166 int index
; /* index of the cached slide */
167 int hid
; /* handle ID of the cached slide */
168 short next
; /* "next" slide, with LRU last */
169 short prev
; /* "previous" slide */
189 struct load_slide_event_data
{
195 struct pfraw_header
{
196 int32_t width
; /* bmap width in pixels */
197 int32_t height
; /* bmap height in pixels */
200 const struct picture logos
[]={
201 {pictureflow_logo
, BMPWIDTH_pictureflow_logo
, BMPHEIGHT_pictureflow_logo
},
204 enum show_album_name_values
{ album_name_hide
= 0, album_name_bottom
,
206 static char* show_album_name_conf
[] =
213 #define MAX_SPACING 40
214 #define MAX_MARGIN 80
216 /* config values and their defaults */
217 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
218 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
219 static int num_slides
= 4;
220 static int zoom
= 100;
221 static bool show_fps
= false;
222 static bool resize
= true;
223 static int cache_version
= 0;
224 static int show_album_name
= (LCD_HEIGHT
> 100)
225 ? album_name_top
: album_name_bottom
;
227 static struct configdata config
[] =
229 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
231 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
233 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
235 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
236 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
237 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
238 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
239 { TYPE_ENUM
, 0, 2, { .int_p
= &show_album_name
}, "show album name",
240 show_album_name_conf
}
243 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
245 /** below we allocate the memory we want to use **/
247 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
248 static uint8_t reflect_table
[REFLECT_HEIGHT
];
249 static struct slide_data center_slide
;
250 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
251 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
252 static int slide_frame
;
256 static int center_index
= 0; /* index of the slide that is in the center */
258 static PFreal offsetX
;
259 static PFreal offsetY
;
260 static int number_of_slides
;
262 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
263 static int cache_free
;
264 static int cache_used
= -1;
265 static int cache_left_index
= -1;
266 static int cache_right_index
= -1;
267 static int cache_center_index
= -1;
269 /* use long for aligning */
270 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
271 /* queue (as array) for scheduling load_surface */
273 static int empty_slide_hid
;
275 unsigned int thread_id
;
276 struct event_queue thread_q
;
278 static struct tagcache_search tcs
;
280 static struct buflib_context buf_ctx
;
282 static struct album_data
*album
;
283 static char *album_names
;
284 static int album_count
;
286 static char track_names
[MAX_TRACKS
* AVG_TRACK_NAME_LENGTH
];
287 static struct track_data tracks
[MAX_TRACKS
];
288 static int track_count
;
289 static int track_index
;
290 static int selected_track
;
291 static int selected_track_pulse
;
292 void reset_track_list(void);
297 static int old_drawmode
;
299 static bool thread_is_running
;
301 static int cover_animation_keyframe
;
302 static int extra_fade
;
304 static int albumtxt_x
= 0;
305 static int albumtxt_dir
= -1;
306 static int prev_center_index
= -1;
308 static int start_index_track_list
= 0;
309 static int track_list_visible_entries
= 0;
310 static int track_list_y
;
311 static int track_list_h
;
312 static int track_scroll_index
= 0;
313 static int track_scroll_dir
= 1;
316 Proposals for transitions:
318 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
319 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
321 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
323 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
326 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
327 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
329 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
342 static inline unsigned fade_color(pix_t c
, unsigned a
);
343 bool save_pfraw(char* filename
, struct bitmap
*bm
);
344 bool load_new_slide(void);
345 int load_surface(int);
347 static inline PFreal
fmul(PFreal a
, PFreal b
)
349 return (a
*b
) >> PFREAL_SHIFT
;
353 * This version preshifts each operand, which is useful when we know how many
354 * of the least significant bits will be empty, or are worried about overflow
355 * in a particular calculation
357 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
359 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
362 /* ARMv5+ has a clz instruction equivalent to our function.
364 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
365 static inline int clz(uint32_t v
)
367 return __builtin_clz(v
);
370 /* Otherwise, use our clz, which can be inlined */
371 #elif defined(CPU_COLDFIRE)
372 /* This clz is based on the log2(n) implementation at
373 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
374 * A clz benchmark plugin showed this to be about 14% faster on coldfire
375 * than the LUT-based version.
377 static inline int clz(uint32_t v
)
409 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
410 0, 0, 0, 0, 0, 0, 0, 0 };
411 /* This clz is based on the log2(n) implementation at
412 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
413 * It is not any faster than the one above, but trades 16B in the lookup table
414 * for a savings of 12B per each inlined call.
416 static inline int clz(uint32_t v
)
434 return r
+ clz_lut
[v
];
438 /* Return the maximum possible left shift for a signed int32, without
441 static inline int allowed_shift(int32_t val
)
443 uint32_t uval
= val
^ (val
>> 31);
444 return clz(uval
) - 1;
447 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
448 * num and den before dividing.
450 static inline PFreal
fdiv(PFreal num
, PFreal den
)
452 int shift
= allowed_shift(num
);
453 shift
= MIN(PFREAL_SHIFT
, shift
);
455 den
>>= PFREAL_SHIFT
- shift
;
459 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
460 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
461 #define fabs(a) (a < 0 ? -a : a)
462 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
464 #if CONFIG_CPU == SH7034
465 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
466 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
468 #define MULUQ(a, b) ((a) * (b))
473 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
474 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
476 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
477 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
479 static inline PFreal
fmul(PFreal a
, PFreal b
)
481 return (a
*b
) >> PFREAL_SHIFT
;
484 static inline PFreal
fdiv(PFreal n
, PFreal m
)
486 return (n
<<(PFREAL_SHIFT
))/m
;
490 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
491 static const short sin_tab
[] = {
492 0, 100, 200, 297, 392, 483, 569, 650,
493 724, 792, 851, 903, 946, 980, 1004, 1019,
494 1024, 1019, 1004, 980, 946, 903, 851, 792,
495 724, 650, 569, 483, 392, 297, 200, 100,
496 0, -100, -200, -297, -392, -483, -569, -650,
497 -724, -792, -851, -903, -946, -980, -1004, -1019,
498 -1024, -1019, -1004, -980, -946, -903, -851, -792,
499 -724, -650, -569, -483, -392, -297, -200, -100,
503 static inline PFreal
fsin(int iangle
)
506 iangle
+= IANGLE_MAX
;
507 iangle
&= IANGLE_MASK
;
509 int i
= (iangle
>> 4);
510 PFreal p
= sin_tab
[i
];
511 PFreal q
= sin_tab
[(i
+1)];
513 return p
+ g
* (iangle
-i
*16)/16;
516 static inline PFreal
fcos(int iangle
)
518 return fsin(iangle
+ (IANGLE_MAX
>> 2));
521 static inline uint32_t div255(uint32_t val
)
523 return ((((val
>> 8) + val
) >> 8) + val
) >> 8;
526 #define SCALE_VAL(val,out) div255((val) * (out) + 127)
528 static void output_row_transposed(uint32_t row
, void * row_in
,
529 struct scaler_context
*ctx
)
531 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
532 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
534 uint32_t *qp
= (uint32_t*)row_in
;
535 for (; dest
< end
; dest
+= ctx
->bm
->height
)
536 *dest
= SC_MUL((*qp
++) + ctx
->round
, ctx
->divisor
);
538 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
539 uint32_t rb_mul
= SCALE_VAL(ctx
->divisor
, 31),
540 rb_rnd
= SCALE_VAL(ctx
->round
, 31),
541 g_mul
= SCALE_VAL(ctx
->divisor
, 63),
542 g_rnd
= SCALE_VAL(ctx
->round
, 63);
544 for (; dest
< end
; dest
+= ctx
->bm
->height
)
546 r
= SC_MUL(qp
->r
+ rb_rnd
, rb_mul
);
547 g
= SC_MUL(qp
->g
+ g_rnd
, g_mul
);
548 b
= SC_MUL(qp
->b
+ rb_rnd
, rb_mul
);
550 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
555 static unsigned int get_size(struct bitmap
*bm
)
557 return bm
->width
* bm
->height
* sizeof(pix_t
);
560 const struct custom_format format_transposed
= {
561 .output_row
= output_row_transposed
,
565 /* Create the lookup table with the scaling values for the reflections */
566 void init_reflect_table(void)
569 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
571 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
572 (5 * REFLECT_HEIGHT
);
576 Create an index of all albums from the database.
577 Also store the album names so we can access them later.
579 int create_album_index(void)
581 buf_size
-= UNIQBUF_SIZE
* sizeof(long);
582 long *uniqbuf
= (long *)(buf_size
+ (char *)buf
);
583 album
= ((struct album_data
*)uniqbuf
) - 1;
584 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
586 rb
->tagcache_search(&tcs
, tag_album
);
587 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
588 unsigned int l
, old_l
= 0;
590 album
[0].name_idx
= 0;
591 while (rb
->tagcache_get_next(&tcs
))
593 buf_size
-= sizeof(struct album_data
);
594 l
= rb
->strlen(tcs
.result
) + 1;
595 if ( album_count
> 0 )
596 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
599 /* not enough memory */
600 return ERROR_BUFFER_FULL
;
602 rb
->strcpy(buf
, tcs
.result
);
604 buf
= l
+ (char *)buf
;
605 album
[-album_count
].seek
= tcs
.result_seek
;
609 rb
->tagcache_search_finish(&tcs
);
610 ALIGN_BUFFER(buf
, buf_size
, 4);
612 struct album_data
* tmp_album
= (struct album_data
*)buf
;
613 for (i
= album_count
- 1; i
>= 0; i
--)
614 tmp_album
[i
] = album
[-i
];
616 buf
= album
+ album_count
;
617 buf_size
+= UNIQBUF_SIZE
* sizeof(long);
618 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
622 Return a pointer to the album name of the given slide_index
624 char* get_album_name(const int slide_index
)
626 return album_names
+ album
[slide_index
].name_idx
;
630 Return a pointer to the track name of the active album
631 create_track_index has to be called first.
633 char* get_track_name(const int track_index
)
635 if ( track_index
< track_count
)
636 return track_names
+ tracks
[track_index
].name_idx
;
641 Create the track index of the given slide_index.
643 int create_track_index(const int slide_index
)
645 if ( slide_index
== track_index
) {
649 if (!rb
->tagcache_search(&tcs
, tag_title
))
653 char temp_titles
[MAX_TRACKS
][AVG_TRACK_NAME_LENGTH
*4];
654 int temp_seeks
[MAX_TRACKS
];
656 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
658 int string_index
= 0;
659 int l
, track_num
, heighest_index
= 0;
661 for(l
=0;l
<MAX_TRACKS
;l
++)
662 temp_titles
[l
][0] = '\0';
663 while (rb
->tagcache_get_next(&tcs
) && track_count
< MAX_TRACKS
)
665 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
) - 1;
668 rb
->snprintf(temp_titles
[track_num
],sizeof(temp_titles
[track_num
]),
669 "%d: %s", track_num
+1, tcs
.result
);
670 temp_seeks
[track_num
] = tcs
.result_seek
;
675 while (temp_titles
[track_num
][0] != '\0')
677 rb
->strcpy(temp_titles
[track_num
], tcs
.result
);
678 temp_seeks
[track_num
] = tcs
.result_seek
;
680 if (track_num
> heighest_index
)
681 heighest_index
= track_num
;
685 rb
->tagcache_search_finish(&tcs
);
686 track_index
= slide_index
;
688 /* now fix the track list order */
691 while (l
<= heighest_index
&&
692 string_index
< MAX_TRACKS
*AVG_TRACK_NAME_LENGTH
)
694 if (temp_titles
[l
][0] != '\0')
696 rb
->strcpy(track_names
+ string_index
, temp_titles
[l
]);
697 tracks
[track_count
].name_idx
= string_index
;
698 tracks
[track_count
].seek
= temp_seeks
[l
];
699 string_index
+= rb
->strlen(temp_titles
[l
]) + 1;
707 return (track_count
> 0) ? 0 : -1;
711 Determine filename of the album art for the given slide_index and
712 store the result in buf.
713 The algorithm looks for the first track of the given album uses
714 find_albumart to find the filename.
716 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
719 if ( slide_index
== -1 )
721 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
724 if (!rb
->tagcache_search(&tcs
, tag_filename
))
728 /* find the first track of the album */
729 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
731 if ( rb
->tagcache_get_next(&tcs
) ) {
735 fd
= rb
->open(tcs
.result
, O_RDONLY
);
736 rb
->get_metadata(&id3
, fd
, tcs
.result
);
738 if ( search_albumart_files(&id3
, "", buf
, buflen
) )
744 /* did not find a matching track */
747 rb
->tagcache_search_finish(&tcs
);
752 Draw the PictureFlow logo
754 void draw_splashscreen(void)
756 struct screen
* display
= rb
->screens
[0];
757 const struct picture
* logo
= &(logos
[display
->screen_type
]);
760 rb
->lcd_set_background(N_BRIGHT(0));
761 rb
->lcd_set_foreground(N_BRIGHT(255));
763 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
765 rb
->lcd_clear_display();
767 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
768 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
769 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
770 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
772 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
780 Draw a simple progress bar
782 void draw_progressbar(int step
)
785 const int bar_height
= 22;
786 const int w
= LCD_WIDTH
- 20;
789 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
791 int y
= (LCD_HEIGHT
- txt_h
)/2;
793 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
797 rb
->lcd_set_foreground(N_BRIGHT(100));
799 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
801 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
804 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
806 rb
->lcd_set_foreground(N_BRIGHT(255));
813 Precomupte the album art images and store them in CACHE_PREFIX.
815 bool create_albumart_cache(void)
820 struct bitmap input_bmp
;
822 char pfraw_file
[MAX_PATH
];
823 char albumart_file
[MAX_PATH
];
824 unsigned int format
= FORMAT_NATIVE
;
826 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
828 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
829 for (i
=0; i
< album_count
; i
++)
831 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
833 /* delete existing cache, so it's a true rebuild */
834 if(rb
->file_exists(pfraw_file
))
835 rb
->remove(pfraw_file
);
837 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
840 input_bmp
.data
= buf
;
841 input_bmp
.width
= DISPLAY_WIDTH
;
842 input_bmp
.height
= DISPLAY_HEIGHT
;
843 ret
= scaled_read_bmp_file(albumart_file
, &input_bmp
,
844 buf_size
, format
, &format_transposed
);
846 rb
->splash(HZ
, "Could not read bmp");
847 continue; /* skip missing/broken files */
849 if (!save_pfraw(pfraw_file
, &input_bmp
))
851 rb
->splash(HZ
, "Could not write bmp");
854 if ( rb
->button_get(false) == PICTUREFLOW_MENU
) return false;
857 /* Warn the user that we couldn't find any albumart */
858 rb
->splash(2*HZ
, "No album art found");
865 Thread used for loading and preparing bitmaps in the background
869 long sleep_time
= 5 * HZ
;
870 struct queue_event ev
;
872 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
877 /* we just woke up */
880 while ( load_new_slide() ) {
892 End the thread by posting the EV_EXIT event
894 void end_pf_thread(void)
896 if ( thread_is_running
) {
897 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
898 rb
->thread_wait(thread_id
);
899 /* remove the thread's queue from the broadcast list */
900 rb
->queue_delete(&thread_q
);
901 thread_is_running
= false;
908 Create the thread an setup the event queue
910 bool create_pf_thread(void)
912 /* put the thread's queue in the bcast list */
913 rb
->queue_init(&thread_q
, true);
914 if ((thread_id
= rb
->create_thread(
917 sizeof(thread_stack
),
919 "Picture load thread"
920 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
921 PRIORITY_REALTIME
+ 1))
927 thread_is_running
= true;
928 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
933 Safe the given bitmap as filename in the pfraw format
935 bool save_pfraw(char* filename
, struct bitmap
*bm
)
937 struct pfraw_header bmph
;
938 bmph
.width
= bm
->width
;
939 bmph
.height
= bm
->height
;
940 int fh
= rb
->creat( filename
);
941 if( fh
< 0 ) return false;
942 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
944 for( y
= 0; y
< bm
->height
; y
++ )
946 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
947 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
955 * The following functions implement the linked-list-in-array used to manage
956 * the LRU cache of slides, and the list of free cache slots.
959 #define seek_right_while(start, cond) \
961 int ind_, next_ = (start); \
964 next_ = cache[ind_].next; \
965 } while (next_ != cache_used && (cond)); \
969 #define seek_left_while(start, cond) \
971 int ind_, next_ = (start); \
974 next_ = cache[ind_].prev; \
975 } while (ind_ != cache_used && (cond)); \
980 Pop the given item from the linked list starting at *head, returning the next
981 item, or -1 if the list is now empty.
983 static inline int lla_pop_item (int *head
, int i
)
985 int prev
= cache
[i
].prev
;
986 int next
= cache
[i
].next
;
994 cache
[next
].prev
= prev
;
995 cache
[prev
].next
= next
;
1001 Pop the head item from the list starting at *head, returning the index of the
1002 item, or -1 if the list is already empty.
1004 static inline int lla_pop_head (int *head
)
1008 lla_pop_item(head
, i
);
1013 Insert the item at index i before the one at index p.
1015 static inline void lla_insert (int i
, int p
)
1018 int prev
= cache
[next
].prev
;
1019 cache
[next
].prev
= i
;
1020 cache
[prev
].next
= i
;
1021 cache
[i
].next
= next
;
1022 cache
[i
].prev
= prev
;
1027 Insert the item at index i at the end of the list starting at *head.
1029 static inline void lla_insert_tail (int *head
, int i
)
1037 lla_insert(i
, *head
);
1041 Insert the item at index i before the one at index p.
1043 static inline void lla_insert_after(int i
, int p
)
1051 Insert the item at index i before the one at index p in the list starting at
1054 static inline void lla_insert_before(int *head
, int i
, int p
)
1063 Free the used slide at index i, and its buffer, and move it to the free
1066 static inline void free_slide(int i
)
1068 if (cache
[i
].hid
!= empty_slide_hid
)
1069 buflib_free(&buf_ctx
, cache
[i
].hid
);
1070 cache
[i
].index
= -1;
1071 lla_pop_item(&cache_used
, i
);
1072 lla_insert_tail(&cache_free
, i
);
1073 if (cache_used
== -1)
1075 cache_right_index
= -1;
1076 cache_left_index
= -1;
1077 cache_center_index
= -1;
1083 Free one slide ranked above the given priority. If no such slide can be found,
1086 static inline int free_slide_prio(int prio
)
1088 if (cache_used
== -1)
1090 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1091 int prio_l
= cache
[l
].index
< center_index
?
1092 center_index
- cache
[l
].index
: 0;
1093 int prio_r
= cache
[r
].index
> center_index
?
1094 cache
[r
].index
- center_index
: 0;
1095 if (prio_l
> prio_r
)
1103 if (prio_max
> prio
)
1105 if (i
== cache_left_index
)
1106 cache_left_index
= cache
[i
].next
;
1107 if (i
== cache_right_index
)
1108 cache_right_index
= cache
[i
].prev
;
1116 Read the pfraw image given as filename and return the hid of the buffer
1118 int read_pfraw(char* filename
, int prio
)
1120 struct pfraw_header bmph
;
1121 int fh
= rb
->open(filename
, O_RDONLY
);
1123 return empty_slide_hid
;
1125 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1127 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1128 bmph
.width
* bmph
.height
;
1131 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1138 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1140 bm
->width
= bmph
.width
;
1141 bm
->height
= bmph
.height
;
1142 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1145 for( y
= 0; y
< bm
->height
; y
++ )
1147 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1156 Load the surface for the given slide_index into the cache at cache_index.
1158 static inline bool load_and_prepare_surface(const int slide_index
,
1159 const int cache_index
,
1162 char tmp_path_name
[MAX_PATH
+1];
1163 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1166 int hid
= read_pfraw(tmp_path_name
, prio
);
1170 cache
[cache_index
].hid
= hid
;
1172 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1173 cache
[cache_index
].index
= slide_index
;
1181 Load the "next" slide that we can load, freeing old slides if needed, provided
1182 that they are further from center_index than the current slide
1184 bool load_new_slide(void)
1187 if (cache_center_index
!= -1)
1190 if (cache
[cache_center_index
].index
!= center_index
)
1192 if (cache
[cache_center_index
].index
< center_index
)
1194 cache_center_index
= seek_right_while(cache_center_index
,
1195 cache
[next_
].index
<= center_index
);
1196 prev
= cache_center_index
;
1197 next
= cache
[cache_center_index
].next
;
1201 cache_center_index
= seek_left_while(cache_center_index
,
1202 cache
[next_
].index
>= center_index
);
1203 next
= cache_center_index
;
1204 prev
= cache
[cache_center_index
].prev
;
1206 if (cache
[cache_center_index
].index
!= center_index
)
1208 if (cache_free
== -1)
1210 i
= lla_pop_head(&cache_free
);
1211 if (!load_and_prepare_surface(center_index
, i
, 0))
1212 goto fail_and_refree
;
1213 if (cache
[next
].index
== -1)
1215 if (cache
[prev
].index
== -1)
1216 goto insert_first_slide
;
1218 next
= cache
[prev
].next
;
1220 lla_insert(i
, next
);
1221 if (cache
[i
].index
< cache
[cache_used
].index
)
1223 cache_center_index
= i
;
1224 cache_left_index
= i
;
1225 cache_right_index
= i
;
1229 if (cache
[cache_left_index
].index
>
1230 cache
[cache_center_index
].index
)
1231 cache_left_index
= cache_center_index
;
1232 if (cache
[cache_right_index
].index
<
1233 cache
[cache_center_index
].index
)
1234 cache_right_index
= cache_center_index
;
1235 cache_left_index
= seek_left_while(cache_left_index
,
1236 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1237 cache_right_index
= seek_right_while(cache_right_index
,
1238 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1239 int prio_l
= cache
[cache_center_index
].index
-
1240 cache
[cache_left_index
].index
+ 1;
1241 int prio_r
= cache
[cache_right_index
].index
-
1242 cache
[cache_center_index
].index
+ 1;
1243 if ((prio_l
< prio_r
||
1244 cache
[cache_right_index
].index
>= number_of_slides
) &&
1245 cache
[cache_left_index
].index
> 0)
1247 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1249 i
= lla_pop_head(&cache_free
);
1250 if (load_and_prepare_surface(cache
[cache_left_index
].index
1253 lla_insert_before(&cache_used
, i
, cache_left_index
);
1254 cache_left_index
= i
;
1257 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1259 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1261 i
= lla_pop_head(&cache_free
);
1262 if (load_and_prepare_surface(cache
[cache_right_index
].index
1265 lla_insert_after(i
, cache_right_index
);
1266 cache_right_index
= i
;
1271 i
= lla_pop_head(&cache_free
);
1272 if (load_and_prepare_surface(center_index
, i
, 0))
1277 cache_center_index
= i
;
1278 cache_left_index
= i
;
1279 cache_right_index
= i
;
1287 lla_insert_tail(&cache_free
, i
);
1294 Get a slide from the buffer
1296 static inline struct dim
*get_slide(const int hid
)
1303 bmp
= buflib_get_data(&buf_ctx
, hid
);
1310 Return the requested surface
1312 static inline struct dim
*surface(const int slide_index
)
1314 if (slide_index
< 0)
1316 if (slide_index
>= number_of_slides
)
1319 if ((i
= cache_used
) != -1)
1322 if (cache
[i
].index
== slide_index
)
1323 return get_slide(cache
[i
].hid
);
1325 } while (i
!= cache_used
);
1327 return get_slide(empty_slide_hid
);
1331 adjust slides so that they are in "steady state" position
1333 void reset_slides(void)
1335 center_slide
.angle
= 0;
1336 center_slide
.cx
= 0;
1337 center_slide
.cy
= 0;
1338 center_slide
.distance
= 0;
1339 center_slide
.slide_index
= center_index
;
1342 for (i
= 0; i
< num_slides
; i
++) {
1343 struct slide_data
*si
= &left_slides
[i
];
1345 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1347 si
->slide_index
= center_index
- 1 - i
;
1351 for (i
= 0; i
< num_slides
; i
++) {
1352 struct slide_data
*si
= &right_slides
[i
];
1354 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1356 si
->slide_index
= center_index
+ 1 + i
;
1363 Updates look-up table and other stuff necessary for the rendering.
1364 Call this when the viewport size or slide dimension is changed.
1366 * To calculate the offset that will provide the proper margin, we use the same
1367 * projection used to render the slides. The solution for xc, the slide center,
1369 * xp * (zo + xs * sin(r))
1370 * xc = xp - xs * cos(r) + ───────────────────────
1372 * TODO: support moving the side slides toward or away from the camera
1374 void recalc_offsets(void)
1376 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1378 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1379 PFREAL_ONE
) * zoom
/ 100;
1382 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1383 cosr
= fcos(-itilt
);
1384 sinr
= fsin(-itilt
);
1385 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1386 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1387 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1388 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1390 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1395 Fade the given color by spreading the fb_data (ushort)
1396 to an uint, multiply and compress the result back to a ushort.
1398 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1399 static inline unsigned fade_color(pix_t c
, unsigned a
)
1401 unsigned int result
;
1403 a
= (a
+ 2) & 0x1fc;
1404 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1405 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1407 return swap16(result
);
1409 #elif LCD_PIXELFORMAT == RGB565
1410 static inline unsigned fade_color(pix_t c
, unsigned a
)
1412 unsigned int result
;
1413 a
= (a
+ 2) & 0x1fc;
1414 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1415 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1420 static inline unsigned fade_color(pix_t c
, unsigned a
)
1423 return MULUQ(val
, a
) >> 8;
1428 * Render a single slide
1429 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1430 * on the slide from its center, zo is the slide's depth offset from the plane
1431 * of the display, r is the angle at which the slide is tilted, and xp is the
1432 * point on the display corresponding to xs on the slide, the projection
1435 * z * (xc + xs * cos(r))
1436 * xp = ──────────────────────
1437 * z + zo + xs * sin(r)
1439 * z * (xc - xp) - xp * zo
1440 * xs = ────────────────────────
1441 * xp * sin(r) - z * cos(r)
1443 * We use the xp projection once, to find the left edge of the slide on the
1444 * display. From there, we use the xs reverse projection to find the horizontal
1445 * offset from the slide center of each column on the screen, until we reach
1446 * the right edge of the slide, or the screen. The reverse projection can be
1447 * optimized by saving the numerator and denominator of the fraction, which can
1448 * then be incremented by (z + zo) and sin(r) respectively.
1450 void render_slide(struct slide_data
*slide
, const int alpha
)
1452 struct dim
*bmp
= surface(slide
->slide_index
);
1456 if (slide
->angle
> 255 || slide
->angle
< -255)
1458 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1460 const int sw
= bmp
->width
;
1461 const int sh
= bmp
->height
;
1462 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1464 const int h
= LCD_HEIGHT
;
1465 const int w
= LCD_WIDTH
;
1468 PFreal cosr
= fcos(slide
->angle
);
1469 PFreal sinr
= fsin(slide
->angle
);
1470 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1471 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1472 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1473 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1474 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1476 /* Since we're finding the screen position of the left edge of the slide,
1479 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1481 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1485 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1486 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1487 xs
= fdiv(xsnum
, xsden
);
1489 xsnumi
= -CAM_DIST_R
- zo
;
1492 int dy
= PFREAL_ONE
;
1493 for (x
= xi
; x
< w
; x
++) {
1494 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1497 if (zo
|| slide
->angle
)
1498 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1499 int y1
= (LCD_HEIGHT
/ 2) - 1;
1501 pix_t
*pixel1
= &buffer
[y1
* BUFFER_WIDTH
+ x
];
1502 pix_t
*pixel2
= pixel1
+ BUFFER_WIDTH
;
1503 const int pixelstep
= BUFFER_WIDTH
;
1505 int p1
= (bmp
->height
- 1 - (DISPLAY_OFFS
)) * PFREAL_ONE
;
1507 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1511 while ((y1
>= 0) && (p1
>= 0))
1513 *pixel1
= ptr
[((unsigned)p1
) >> PFREAL_SHIFT
];
1516 pixel1
-= pixelstep
;
1518 while ((p2
< sh
* PFREAL_ONE
) && (y2
< h
))
1520 *pixel2
= ptr
[((unsigned)p2
) >> PFREAL_SHIFT
];
1523 pixel2
+= pixelstep
;
1525 while ((p2
< MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
) &&
1528 int ty
= (((unsigned)p2
) >> PFREAL_SHIFT
) - sh
;
1529 int lalpha
= reflect_table
[ty
];
1530 *pixel2
= fade_color(ptr
[sh
- 1 - ty
],lalpha
);
1533 pixel2
+= pixelstep
;
1538 while ((y1
>= 0) && (p1
>= 0))
1540 *pixel1
= fade_color(ptr
[((unsigned)p1
) >> PFREAL_SHIFT
],alpha
);
1543 pixel1
-= pixelstep
;
1545 while ((p2
< sh
* PFREAL_ONE
) && (y2
< h
))
1547 *pixel2
= fade_color(ptr
[((unsigned)p2
) >> PFREAL_SHIFT
],alpha
);
1550 pixel2
+= pixelstep
;
1552 while ((p2
< MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
) &&
1555 int ty
= (((unsigned)p2
) >> PFREAL_SHIFT
) - sh
;
1556 int lalpha
= reflect_table
[ty
];
1557 lalpha
= (MULUQ(lalpha
, alpha
) + 128) >> 8;
1558 *pixel2
= fade_color(ptr
[sh
- 1 - ty
],lalpha
);
1561 pixel2
+= pixelstep
;
1564 if (zo
|| slide
->angle
)
1568 xs
= fdiv(xsnum
, xsden
);
1573 /* let the music play... */
1580 Jump the the given slide_index
1582 static inline void set_current_slide(const int slide_index
)
1584 int old_center_index
= center_index
;
1586 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1587 if (old_center_index
!= center_index
)
1588 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1589 target
= center_index
;
1590 slide_frame
= slide_index
<< 16;
1595 Start the animation for changing slides
1597 void start_animation(void)
1599 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1600 pf_state
= pf_scrolling
;
1604 Go to the previous slide
1606 void show_previous_slide(void)
1609 if (center_index
> 0) {
1610 target
= center_index
- 1;
1613 } else if ( step
> 0 ) {
1614 target
= center_index
;
1617 target
= fmax(0, center_index
- 2);
1623 Go to the next slide
1625 void show_next_slide(void)
1628 if (center_index
< number_of_slides
- 1) {
1629 target
= center_index
+ 1;
1632 } else if ( step
< 0 ) {
1633 target
= center_index
;
1636 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1642 Render the slides. Updates only the offscreen buffer.
1644 void render_all_slides(void)
1646 MYLCD(set_background
)(G_BRIGHT(0));
1647 /* TODO: Optimizes this by e.g. invalidating rects */
1648 MYLCD(clear_display
)();
1650 int nleft
= num_slides
;
1651 int nright
= num_slides
;
1655 /* no animation, boring plain rendering */
1656 for (index
= nleft
- 2; index
>= 0; index
--) {
1657 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1658 alpha
-= extra_fade
;
1660 render_slide(&left_slides
[index
], alpha
);
1662 for (index
= nright
- 2; index
>= 0; index
--) {
1663 int alpha
= (index
< nright
- 2) ? 256 : 128;
1664 alpha
-= extra_fade
;
1666 render_slide(&right_slides
[index
], alpha
);
1669 /* the first and last slide must fade in/fade out */
1670 for (index
= nleft
- 1; index
>= 0; index
--) {
1672 if (index
== nleft
- 1)
1673 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1674 if (index
== nleft
- 2)
1675 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1676 if (index
== nleft
- 3)
1677 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1678 render_slide(&left_slides
[index
], alpha
);
1680 for (index
= nright
- 1; index
>= 0; index
--) {
1681 int alpha
= (index
< nright
- 2) ? 256 : 128;
1682 if (index
== nright
- 1)
1683 alpha
= (step
> 0) ? fade
/ 2 : 0;
1684 if (index
== nright
- 2)
1685 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1686 if (index
== nright
- 3)
1687 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1688 render_slide(&right_slides
[index
], alpha
);
1691 render_slide(¢er_slide
, 256);
1696 Updates the animation effect. Call this periodically from a timer.
1698 void update_scroll_animation(void)
1706 /* deaccelerate when approaching the target */
1708 const int max
= 2 * 65536;
1710 int fi
= slide_frame
;
1711 fi
-= (target
<< 16);
1716 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1717 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1720 slide_frame
+= speed
* step
;
1722 int index
= slide_frame
>> 16;
1723 int pos
= slide_frame
& 0xffff;
1724 int neg
= 65536 - pos
;
1725 int tick
= (step
< 0) ? neg
: pos
;
1726 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1728 /* the leftmost and rightmost slide must fade away */
1733 if (center_index
!= index
) {
1734 center_index
= index
;
1735 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1736 slide_frame
= index
<< 16;
1737 center_slide
.slide_index
= center_index
;
1738 for (i
= 0; i
< num_slides
; i
++)
1739 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1740 for (i
= 0; i
< num_slides
; i
++)
1741 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1744 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1745 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1746 center_slide
.cy
= fmul(offsetY
, ftick
);
1748 if (center_index
== target
) {
1756 for (i
= 0; i
< num_slides
; i
++) {
1757 struct slide_data
*si
= &left_slides
[i
];
1760 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1761 * slide_spacing
* ftick
);
1765 for (i
= 0; i
< num_slides
; i
++) {
1766 struct slide_data
*si
= &right_slides
[i
];
1769 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1770 * slide_spacing
* ftick
;
1775 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1776 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1777 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1778 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1780 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1781 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1782 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1783 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1786 /* must change direction ? */
1799 void cleanup(void *parameter
)
1802 /* Turn on backlight timeout (revert to settings) */
1803 backlight_use_settings(); /* backlight control in lib/helper.c */
1808 rb
->lcd_set_drawmode(old_drawmode
);
1812 Create the "?" slide, that is shown while loading
1813 or when no cover was found.
1815 int create_empty_slide(bool force
)
1817 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1818 struct bitmap input_bmp
;
1820 input_bmp
.width
= DISPLAY_WIDTH
;
1821 input_bmp
.height
= DISPLAY_HEIGHT
;
1823 input_bmp
.format
= FORMAT_NATIVE
;
1825 input_bmp
.data
= (char*)buf
;
1826 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1828 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1829 &format_transposed
);
1830 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1838 Shows the album name setting menu
1840 int album_name_menu(void)
1842 int selection
= show_album_name
;
1844 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
1845 "Hide album title", "Show at the bottom", "Show at the top");
1846 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
1848 show_album_name
= selection
;
1849 return GO_TO_PREVIOUS
;
1853 Shows the settings menu
1855 int settings_menu(void)
1860 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1861 "Spacing", "Centre margin", "Number of slides", "Zoom",
1862 "Show album title", "Resize Covers", "Rebuild cache");
1865 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1868 rb
->set_bool("Show FPS", &show_fps
);
1873 rb
->set_int("Spacing between slides", "", 1,
1875 NULL
, 1, 0, 100, NULL
);
1881 rb
->set_int("Centre margin", "", 1,
1883 NULL
, 1, 0, 80, NULL
);
1889 rb
->set_int("Number of slides", "", 1, &num_slides
,
1890 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
1896 rb
->set_int("Zoom", "", 1, &zoom
,
1897 NULL
, 1, 10, 300, NULL
);
1909 rb
->set_bool("Resize Covers", &resize
);
1910 if (old_val
== resize
) /* changed? */
1912 /* fallthrough if changed, since cache needs to be rebuilt */
1915 rb
->remove(EMPTY_SLIDE
);
1916 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
1919 case MENU_ATTACHED_USB
:
1920 return PLUGIN_USB_CONNECTED
;
1922 } while ( selection
>= 0 );
1923 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
1936 rb
->lcd_set_foreground(N_BRIGHT(255));
1939 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
1940 "Settings", "Return", "Quit");
1942 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
1944 result
= settings_menu();
1945 if ( result
!= 0 ) return result
;
1954 case MENU_ATTACHED_USB
:
1955 return PLUGIN_USB_CONNECTED
;
1964 Animation step for zooming into the current cover
1966 void update_cover_in_animation(void)
1968 cover_animation_keyframe
++;
1969 if( cover_animation_keyframe
< 20 ) {
1970 center_slide
.distance
-=5;
1971 center_slide
.angle
+=1;
1974 else if( cover_animation_keyframe
< 35 ) {
1975 center_slide
.angle
+=16;
1978 cover_animation_keyframe
= 0;
1979 pf_state
= pf_show_tracks
;
1984 Animation step for zooming out the current cover
1986 void update_cover_out_animation(void)
1988 cover_animation_keyframe
++;
1989 if( cover_animation_keyframe
<= 15 ) {
1990 center_slide
.angle
-=16;
1992 else if( cover_animation_keyframe
< 35 ) {
1993 center_slide
.distance
+=5;
1994 center_slide
.angle
-=1;
1998 cover_animation_keyframe
= 0;
2004 Draw a blue gradient at y with height h
2006 static inline void draw_gradient(int y
, int h
)
2008 static int r
, inc
, c
;
2009 inc
= (100 << 8) / h
;
2011 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2012 int c2
= selected_track_pulse
- 5;
2013 for (r
=0; r
<h
; r
++) {
2014 #ifdef HAVE_LCD_COLOR
2015 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2018 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2020 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2029 static void track_list_yh(int char_height
)
2031 switch (show_album_name
)
2033 case album_name_hide
:
2034 track_list_y
= (show_fps
? char_height
: 0);
2035 track_list_h
= LCD_HEIGHT
- track_list_y
;
2037 case album_name_bottom
:
2038 track_list_y
= (show_fps
? char_height
: 0);
2039 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2041 default: /* case album_name_top */
2042 track_list_y
= char_height
* 2;
2043 track_list_h
= LCD_HEIGHT
- track_list_y
-
2044 (show_fps
? char_height
: 0);
2050 Reset the track list after a album change
2052 void reset_track_list(void)
2054 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2055 track_list_yh(albumtxt_h
);
2056 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2057 start_index_track_list
= 0;
2058 track_scroll_index
= 0;
2059 track_scroll_dir
= 1;
2062 /* let the tracklist start more centered
2063 * if the screen isn't filled with tracks */
2064 if (track_count
*albumtxt_h
< track_list_h
)
2066 track_list_h
= track_count
* albumtxt_h
;
2067 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2072 Display the list of tracks
2074 void show_track_list(void)
2076 MYLCD(clear_display
)();
2077 if ( center_slide
.slide_index
!= track_index
) {
2078 create_track_index(center_slide
.slide_index
);
2081 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2082 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2084 int titletxt_y
= track_list_y
;
2086 track_i
= start_index_track_list
;
2087 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2090 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2091 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2092 if ( track_i
== selected_track
) {
2093 draw_gradient(titletxt_y
, titletxt_h
);
2094 MYLCD(set_foreground
)(G_BRIGHT(255));
2095 if (titletxt_w
> LCD_WIDTH
) {
2096 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2097 track_scroll_dir
= 1;
2098 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2099 track_scroll_index
+= track_scroll_dir
*2;
2100 titletxt_x
= track_scroll_index
;
2102 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2105 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2106 MYLCD(set_foreground
)(G_BRIGHT(color
));
2107 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2109 titletxt_y
+= titletxt_h
;
2113 void select_next_track(void)
2115 if ( selected_track
< track_count
- 1 ) {
2117 track_scroll_index
= 0;
2118 track_scroll_dir
= 1;
2119 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2120 start_index_track_list
++;
2124 void select_prev_track(void)
2126 if (selected_track
> 0 ) {
2127 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2128 track_scroll_index
= 0;
2129 track_scroll_dir
= 1;
2135 Draw the current album name
2137 void draw_album_text(void)
2139 if (0 == show_album_name
)
2142 int albumtxt_w
, albumtxt_h
;
2147 /* Draw album text */
2148 if ( pf_state
== pf_scrolling
) {
2149 c
= ((slide_frame
& 0xffff )/ 255);
2150 if (step
< 0) c
= 255-c
;
2151 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2152 albumtxt
= get_album_name(center_index
+step
);
2156 albumtxt
= get_album_name(center_index
);
2162 albumtxt
= get_album_name(center_index
);
2165 MYLCD(set_foreground
)(G_BRIGHT(c
));
2166 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2167 if (center_index
!= prev_center_index
) {
2170 prev_center_index
= center_index
;
2173 if (show_album_name
== album_name_top
)
2174 albumtxt_y
= albumtxt_h
/ 2;
2176 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2178 if (albumtxt_w
> LCD_WIDTH
) {
2179 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2180 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2181 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2182 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2183 albumtxt_x
+= albumtxt_dir
;
2187 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2195 Main function that also contain the main plasma
2202 rb
->lcd_setfont(FONT_UI
);
2203 draw_splashscreen();
2205 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2206 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2207 rb
->splash(HZ
, "Could not create directory " CACHE_PREFIX
);
2208 return PLUGIN_ERROR
;
2212 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2214 init_reflect_table();
2216 ALIGN_BUFFER(buf
, buf_size
, 4);
2217 ret
= create_album_index();
2218 if (ret
== ERROR_BUFFER_FULL
) {
2219 rb
->splash(HZ
, "Not enough memory for album names");
2220 return PLUGIN_ERROR
;
2221 } else if (ret
== ERROR_NO_ALBUMS
) {
2222 rb
->splash(HZ
, "No albums found. Please enable database");
2223 return PLUGIN_ERROR
;
2226 ALIGN_BUFFER(buf
, buf_size
, 4);
2227 number_of_slides
= album_count
;
2228 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2229 rb
->splash(HZ
, "Could not create album art cache");
2230 return PLUGIN_ERROR
;
2233 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2234 rb
->splash(HZ
, "Could not load the empty slide");
2235 return PLUGIN_ERROR
;
2237 cache_version
= CACHE_VERSION
;
2238 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2243 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2244 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2246 rb
->splash(HZ
, "Greylib init failed!");
2247 return PLUGIN_ERROR
;
2249 grey_setfont(FONT_UI
);
2250 buf_size
-= grey_buf_used
;
2251 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2253 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2255 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2257 rb
->splash(HZ
, "Unable to load empty slide image");
2258 return PLUGIN_ERROR
;
2261 if (!create_pf_thread()) {
2262 rb
->splash(HZ
, "Cannot create thread!");
2263 return PLUGIN_ERROR
;
2269 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2272 cache
[i
].next
= i
+ 1;
2273 cache
[i
].prev
= i
- 1;
2275 cache
[0].prev
= i
- 1;
2276 cache
[i
- 1].next
= 0;
2296 long last_update
= *rb
->current_tick
;
2297 long current_update
;
2298 long update_interval
= 100;
2302 bool instant_update
;
2303 old_drawmode
= rb
->lcd_get_drawmode();
2306 grey_set_drawmode(DRMODE_FG
);
2308 rb
->lcd_set_drawmode(DRMODE_FG
);
2310 current_update
= *rb
->current_tick
;
2313 /* Initial rendering */
2314 instant_update
= false;
2317 switch ( pf_state
) {
2319 update_scroll_animation();
2320 render_all_slides();
2321 instant_update
= true;
2324 update_cover_in_animation();
2325 render_all_slides();
2326 instant_update
= true;
2329 update_cover_out_animation();
2330 render_all_slides();
2331 instant_update
= true;
2333 case pf_show_tracks
:
2337 render_all_slides();
2342 if (current_update
- last_update
> update_interval
) {
2343 fps
= frames
* HZ
/ (current_update
- last_update
);
2344 last_update
= current_update
;
2351 MYLCD(set_foreground
)(G_BRIGHT(255));
2353 MYLCD(set_foreground
)(G_PIX(255,0,0));
2355 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2356 if (show_album_name
== album_name_top
)
2357 fpstxt_y
= LCD_HEIGHT
-
2358 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2361 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2366 /* Copy offscreen buffer to LCD and give time to other threads */
2370 /*/ Handle buttons */
2371 button
= pluginlib_getaction(instant_update
? 0 : HZ
/16,
2372 plugin_contexts
, NB_ACTION_CONTEXTS
);
2375 case PICTUREFLOW_QUIT
:
2378 case PICTUREFLOW_MENU
:
2379 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
) {
2384 if ( ret
== -1 ) return PLUGIN_OK
;
2385 if ( ret
!= 0 ) return i
;
2389 MYLCD(set_drawmode
)(DRMODE_FG
);
2392 pf_state
= pf_cover_out
;
2396 case PICTUREFLOW_NEXT_ALBUM
:
2397 case PICTUREFLOW_NEXT_ALBUM_REPEAT
:
2398 #ifdef HAVE_SCROLLWHEEL
2399 if ( pf_state
== pf_show_tracks
)
2400 select_next_track();
2402 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2406 case PICTUREFLOW_PREV_ALBUM
:
2407 case PICTUREFLOW_PREV_ALBUM_REPEAT
:
2408 #ifdef HAVE_SCROLLWHEEL
2409 if ( pf_state
== pf_show_tracks
)
2410 select_prev_track();
2412 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2413 show_previous_slide();
2416 #ifndef HAVE_SCROLLWHEEL
2417 case PICTUREFLOW_NEXT_TRACK
:
2418 case PICTUREFLOW_NEXT_TRACK_REPEAT
:
2419 if ( pf_state
== pf_show_tracks
)
2420 select_next_track();
2423 case PICTUREFLOW_PREV_TRACK
:
2424 case PICTUREFLOW_PREV_TRACK_REPEAT
:
2425 if ( pf_state
== pf_show_tracks
)
2426 select_prev_track();
2430 case PICTUREFLOW_SELECT_ALBUM
:
2431 if ( pf_state
== pf_idle
) {
2432 pf_state
= pf_cover_in
;
2434 if ( pf_state
== pf_show_tracks
)
2435 pf_state
= pf_cover_out
;
2439 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2440 == SYS_USB_CONNECTED
)
2441 return PLUGIN_USB_CONNECTED
;
2449 /*************************** Plugin entry point ****************************/
2451 enum plugin_status
plugin_start(const void *parameter
)
2456 rb
->lcd_set_backdrop(NULL
);
2458 /* Turn off backlight timeout */
2459 backlight_force_on(); /* backlight control in lib/helper.c */
2460 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2461 rb
->cpu_boost(true);
2463 #if PLUGIN_BUFFER_SIZE > 0x10000
2464 buf
= rb
->plugin_get_buffer(&buf_size
);
2466 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2469 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2470 rb
->cpu_boost(false);
2472 if ( ret
== PLUGIN_OK
) {
2473 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2476 rb
->splash(HZ
, "Error writing config.");