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/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/helper.h"
31 #include "lib/configfile.h"
33 #include "lib/feature_wrappers.h"
34 #include "lib/buflib.h"
38 /******************************* Globals ***********************************/
41 * Targets which use plugin_get_audio_buffer() can't have playback from
42 * within pictureflow itself, as the whole core audio buffer is occupied */
43 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
45 #if PF_PLAYBACK_CAPABLE
46 #include "lib/playback_control.h"
49 #define PF_PREV ACTION_STD_PREV
50 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
51 #define PF_NEXT ACTION_STD_NEXT
52 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
53 #define PF_SELECT ACTION_STD_OK
54 #define PF_CONTEXT ACTION_STD_CONTEXT
55 #define PF_BACK ACTION_STD_CANCEL
56 #define PF_MENU ACTION_STD_MENU
57 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
59 #if !defined(HAVE_SCROLLWHEEL)
60 /* scrollwheel targets use the wheel, just as they do in lists,
61 * so there's no need for a special context,
62 * others use left/right here too (as oppsed to up/down in lists) */
63 const struct button_mapping pf_context_album_scroll
[] =
65 #ifdef HAVE_TOUCHSCREEN
66 {PF_PREV
, BUTTON_MIDLEFT
, BUTTON_NONE
},
67 {PF_PREV_REPEAT
, BUTTON_MIDLEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
68 {PF_NEXT
, BUTTON_MIDRIGHT
, BUTTON_NONE
},
69 {PF_NEXT_REPEAT
, BUTTON_MIDRIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
71 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
72 {PF_PREV
, BUTTON_RC_REW
, BUTTON_NONE
},
73 {PF_PREV_REPEAT
, BUTTON_RC_REW
|BUTTON_REPEAT
,BUTTON_NONE
},
74 {PF_NEXT
, BUTTON_RC_FF
, BUTTON_NONE
},
75 {PF_NEXT_REPEAT
, BUTTON_RC_FF
|BUTTON_REPEAT
, BUTTON_NONE
},
77 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
78 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
79 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
80 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
81 {ACTION_NONE
, BUTTON_LEFT
|BUTTON_REL
, BUTTON_LEFT
},
82 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REL
, BUTTON_RIGHT
},
83 {ACTION_NONE
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_LEFT
},
84 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_RIGHT
},
86 #if CONFIG_KEYPAD == ONDIO_PAD
87 {PF_SELECT
, BUTTON_UP
|BUTTON_REL
, BUTTON_UP
},
88 {PF_CONTEXT
, BUTTON_UP
|BUTTON_REPEAT
, BUTTON_UP
},
89 {ACTION_NONE
, BUTTON_UP
, BUTTON_NONE
},
90 {ACTION_NONE
, BUTTON_DOWN
, BUTTON_NONE
},
91 {ACTION_NONE
, BUTTON_DOWN
|BUTTON_REPEAT
, BUTTON_NONE
},
93 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN
|1)
95 #endif /* !defined(HAVE_SCROLLWHEEL) */
97 const struct button_mapping pf_context_buttons
[] =
99 #ifdef HAVE_TOUCHSCREEN
100 {PF_SELECT
, BUTTON_CENTER
, BUTTON_NONE
},
101 {PF_MENU
, BUTTON_TOPLEFT
, BUTTON_NONE
},
102 {PF_BACK
, BUTTON_BOTTOMRIGHT
, BUTTON_NONE
},
104 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
105 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
106 #elif CONFIG_KEYPAD == SANSA_C100_PAD
107 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
108 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
109 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
110 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
111 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
112 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
113 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
114 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
115 {PF_QUIT
, BUTTON_HOME
|BUTTON_REPEAT
, BUTTON_NONE
},
116 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
118 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
119 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
120 {PF_QUIT
, BUTTON_POWER
|BUTTON_REPEAT
, BUTTON_POWER
},
121 #if CONFIG_KEYPAD == COWOND2_PAD
122 {PF_BACK
, BUTTON_POWER
|BUTTON_REL
, BUTTON_POWER
},
123 {ACTION_NONE
, BUTTON_POWER
, BUTTON_NONE
},
125 #elif CONFIG_KEYPAD == SANSA_E200_PAD
126 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
127 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
128 {PF_QUIT
, BUTTON_EQ
, BUTTON_NONE
},
129 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
130 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
131 || (CONFIG_KEYPAD == IPOD_4G_PAD)
132 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
133 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
134 {PF_QUIT
, BUTTON_POWERPLAY
|BUTTON_REPEAT
, BUTTON_POWERPLAY
},
135 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
136 {PF_QUIT
, BUTTON_RC_REC
, BUTTON_NONE
},
137 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
138 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
139 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
140 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
141 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
143 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
144 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
|CONTEXT_REMOTE
)
146 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE
)
149 const struct button_mapping
*pf_contexts
[] =
151 #if !defined(HAVE_SCROLLWHEEL)
152 pf_context_album_scroll
,
159 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
160 #else /* LCD_DEPTH <= 1 */
161 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
162 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
163 #define PICTUREFLOW_DRMODE DRMODE_SOLID
165 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
167 #endif /* LCD_DEPTH <= 1 */
170 #define LCD_BUF _grey_info.buffer
171 #define MYLCD(fn) grey_ ## fn
172 #define G_PIX(r,g,b) \
173 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
174 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
175 #define G_BRIGHT(y) (y)
176 #define BUFFER_WIDTH _grey_info.width
177 #define BUFFER_HEIGHT _grey_info.height
178 typedef unsigned char pix_t
;
179 #else /* LCD_DEPTH >= 8 */
180 #define LCD_BUF rb->lcd_framebuffer
181 #define MYLCD(fn) rb->lcd_ ## fn
182 #define G_PIX LCD_RGBPACK
183 #define N_PIX LCD_RGBPACK
184 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
185 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
186 #define BUFFER_WIDTH LCD_WIDTH
187 #define BUFFER_HEIGHT LCD_HEIGHT
188 typedef fb_data pix_t
;
189 #endif /* LCD_DEPTH >= 8 */
191 /* for fixed-point arithmetic, we need minimum 32-bit long
192 long long (64-bit) might be useful for multiplication and division */
194 #define PFREAL_SHIFT 10
195 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
196 #define PFREAL_ONE (1 << PFREAL_SHIFT)
197 #define PFREAL_HALF (PFREAL_ONE >> 1)
200 #define IANGLE_MAX 1024
201 #define IANGLE_MASK 1023
203 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
204 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
205 #define DISPLAY_HEIGHT REFLECT_TOP
206 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
207 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
208 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
209 (REFLECT_HEIGHT * 5))
210 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
211 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
212 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
213 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
214 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
216 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
218 #define MAX_SLIDES_COUNT 10
220 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
221 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
224 #define EV_WAKEUP 1337
226 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
227 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
228 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
230 /* Error return values */
231 #define ERROR_NO_ALBUMS -1
232 #define ERROR_BUFFER_FULL -2
234 /* current version for cover cache */
235 #define CACHE_VERSION 3
236 #define CONFIG_VERSION 1
237 #define CONFIG_FILE "pictureflow.cfg"
239 /** structs we use */
250 int index
; /* index of the cached slide */
251 int hid
; /* handle ID of the cached slide */
252 short next
; /* "next" slide, with LRU last */
253 short prev
; /* "previous" slide */
263 int name_idx
; /* offset to the track name */
265 #if PF_PLAYBACK_CAPABLE
266 /* offset to the filename in the string, needed for playlist generation */
278 struct load_slide_event_data
{
284 struct pfraw_header
{
285 int32_t width
; /* bmap width in pixels */
286 int32_t height
; /* bmap height in pixels */
289 enum show_album_name_values
{ album_name_hide
= 0, album_name_bottom
,
291 static char* show_album_name_conf
[] =
298 #define MAX_SPACING 40
299 #define MAX_MARGIN 80
301 /* config values and their defaults */
302 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
303 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
304 static int num_slides
= 4;
305 static int zoom
= 100;
306 static bool show_fps
= false;
307 static bool resize
= true;
308 static int cache_version
= 0;
309 static int show_album_name
= (LCD_HEIGHT
> 100)
310 ? album_name_top
: album_name_bottom
;
312 static struct configdata config
[] =
314 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
316 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
318 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
320 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
321 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
322 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
323 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
324 { TYPE_ENUM
, 0, 2, { .int_p
= &show_album_name
}, "show album name",
325 show_album_name_conf
}
328 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
330 /** below we allocate the memory we want to use **/
332 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
333 static uint8_t reflect_table
[REFLECT_HEIGHT
];
334 static struct slide_data center_slide
;
335 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
336 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
337 static int slide_frame
;
341 static int center_index
= 0; /* index of the slide that is in the center */
343 static PFreal offsetX
;
344 static PFreal offsetY
;
345 static int number_of_slides
;
347 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
348 static int cache_free
;
349 static int cache_used
= -1;
350 static int cache_left_index
= -1;
351 static int cache_right_index
= -1;
352 static int cache_center_index
= -1;
354 /* use long for aligning */
355 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
356 /* queue (as array) for scheduling load_surface */
358 static int empty_slide_hid
;
360 unsigned int thread_id
;
361 struct event_queue thread_q
;
363 static struct tagcache_search tcs
;
365 static struct buflib_context buf_ctx
;
367 static struct album_data
*album
;
368 static char *album_names
;
369 static int album_count
;
371 static struct track_data
*tracks
;
372 static char *track_names
;
373 static size_t borrowed
= 0;
374 static int track_count
;
375 static int track_index
;
376 static int selected_track
;
377 static int selected_track_pulse
;
378 void reset_track_list(void);
383 static bool thread_is_running
;
385 static int cover_animation_keyframe
;
386 static int extra_fade
;
388 static int albumtxt_x
= 0;
389 static int albumtxt_dir
= -1;
390 static int prev_center_index
= -1;
392 static int start_index_track_list
= 0;
393 static int track_list_visible_entries
= 0;
394 static int track_list_y
;
395 static int track_list_h
;
396 static int track_scroll_index
= 0;
397 static int track_scroll_dir
= 1;
400 Proposals for transitions:
402 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
403 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
405 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
407 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
410 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
411 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
413 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
426 static bool free_slide_prio(int prio
);
427 static inline unsigned fade_color(pix_t c
, unsigned a
);
428 bool save_pfraw(char* filename
, struct bitmap
*bm
);
429 bool load_new_slide(void);
430 int load_surface(int);
432 static inline PFreal
fmul(PFreal a
, PFreal b
)
434 return (a
*b
) >> PFREAL_SHIFT
;
438 * This version preshifts each operand, which is useful when we know how many
439 * of the least significant bits will be empty, or are worried about overflow
440 * in a particular calculation
442 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
444 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
447 /* ARMv5+ has a clz instruction equivalent to our function.
449 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
450 static inline int clz(uint32_t v
)
452 return __builtin_clz(v
);
455 /* Otherwise, use our clz, which can be inlined */
456 #elif defined(CPU_COLDFIRE)
457 /* This clz is based on the log2(n) implementation at
458 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
459 * A clz benchmark plugin showed this to be about 14% faster on coldfire
460 * than the LUT-based version.
462 static inline int clz(uint32_t v
)
494 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
495 0, 0, 0, 0, 0, 0, 0, 0 };
496 /* This clz is based on the log2(n) implementation at
497 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
498 * It is not any faster than the one above, but trades 16B in the lookup table
499 * for a savings of 12B per each inlined call.
501 static inline int clz(uint32_t v
)
519 return r
+ clz_lut
[v
];
523 /* Return the maximum possible left shift for a signed int32, without
526 static inline int allowed_shift(int32_t val
)
528 uint32_t uval
= val
^ (val
>> 31);
529 return clz(uval
) - 1;
532 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
533 * num and den before dividing.
535 static inline PFreal
fdiv(PFreal num
, PFreal den
)
537 int shift
= allowed_shift(num
);
538 shift
= MIN(PFREAL_SHIFT
, shift
);
540 den
>>= PFREAL_SHIFT
- shift
;
544 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
545 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
546 #define fabs(a) (a < 0 ? -a : a)
547 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
549 #if CONFIG_CPU == SH7034
550 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
551 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
553 #define MULUQ(a, b) ((a) * (b))
558 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
559 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
561 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
562 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
564 static inline PFreal
fmul(PFreal a
, PFreal b
)
566 return (a
*b
) >> PFREAL_SHIFT
;
569 static inline PFreal
fdiv(PFreal n
, PFreal m
)
571 return (n
<<(PFREAL_SHIFT
))/m
;
575 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
576 static const short sin_tab
[] = {
577 0, 100, 200, 297, 392, 483, 569, 650,
578 724, 792, 851, 903, 946, 980, 1004, 1019,
579 1024, 1019, 1004, 980, 946, 903, 851, 792,
580 724, 650, 569, 483, 392, 297, 200, 100,
581 0, -100, -200, -297, -392, -483, -569, -650,
582 -724, -792, -851, -903, -946, -980, -1004, -1019,
583 -1024, -1019, -1004, -980, -946, -903, -851, -792,
584 -724, -650, -569, -483, -392, -297, -200, -100,
588 static inline PFreal
fsin(int iangle
)
590 iangle
&= IANGLE_MASK
;
592 int i
= (iangle
>> 4);
593 PFreal p
= sin_tab
[i
];
594 PFreal q
= sin_tab
[(i
+1)];
596 return p
+ g
* (iangle
-i
*16)/16;
599 static inline PFreal
fcos(int iangle
)
601 return fsin(iangle
+ (IANGLE_MAX
>> 2));
604 static inline unsigned scale_val(unsigned val
, unsigned bits
)
606 val
= val
* ((1 << bits
) - 1);
607 return ((val
>> 8) + val
+ 128) >> 8;
610 static void output_row_8_transposed(uint32_t row
, void * row_in
,
611 struct scaler_context
*ctx
)
613 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
614 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
616 uint8_t *qp
= (uint8_t*)row_in
;
617 for (; dest
< end
; dest
+= ctx
->bm
->height
)
620 struct uint8_rgb
*qp
= (struct uint8_rgb
*)row_in
;
622 for (; dest
< end
; dest
+= ctx
->bm
->height
)
624 r
= scale_val(qp
->red
, 5);
625 g
= scale_val(qp
->green
, 6);
626 b
= scale_val((qp
++)->blue
, 5);
627 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
632 static void output_row_32_transposed(uint32_t row
, void * row_in
,
633 struct scaler_context
*ctx
)
635 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
636 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
638 uint32_t *qp
= (uint32_t*)row_in
;
639 for (; dest
< end
; dest
+= ctx
->bm
->height
)
640 *dest
= SC_OUT(*qp
++, ctx
);
642 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
644 for (; dest
< end
; dest
+= ctx
->bm
->height
)
646 r
= scale_val(SC_OUT(qp
->r
, ctx
), 5);
647 g
= scale_val(SC_OUT(qp
->g
, ctx
), 6);
648 b
= scale_val(SC_OUT(qp
->b
, ctx
), 5);
650 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
655 #ifdef HAVE_LCD_COLOR
656 static void output_row_32_transposed_fromyuv(uint32_t row
, void * row_in
,
657 struct scaler_context
*ctx
)
659 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
660 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
661 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
662 for (; dest
< end
; dest
+= ctx
->bm
->height
)
664 unsigned r
, g
, b
, y
, u
, v
;
665 y
= SC_OUT(qp
->b
, ctx
);
666 u
= SC_OUT(qp
->g
, ctx
);
667 v
= SC_OUT(qp
->r
, ctx
);
669 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
673 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
678 static unsigned int get_size(struct bitmap
*bm
)
680 return bm
->width
* bm
->height
* sizeof(pix_t
);
683 const struct custom_format format_transposed
= {
684 .output_row_8
= output_row_8_transposed
,
685 #ifdef HAVE_LCD_COLOR
687 output_row_32_transposed
,
688 output_row_32_transposed_fromyuv
691 .output_row_32
= output_row_32_transposed
,
696 static const struct button_mapping
* get_context_map(int context
)
698 return pf_contexts
[context
& ~CONTEXT_PLUGIN
];
701 /* Create the lookup table with the scaling values for the reflections */
702 void init_reflect_table(void)
705 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
707 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
708 (5 * REFLECT_HEIGHT
);
712 Create an index of all albums from the database.
713 Also store the album names so we can access them later.
715 int create_album_index(void)
717 album
= ((struct album_data
*)(buf_size
+ (char *) buf
)) - 1;
718 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
720 rb
->tagcache_search(&tcs
, tag_album
);
721 unsigned int l
, old_l
= 0;
723 album
[0].name_idx
= 0;
724 while (rb
->tagcache_get_next(&tcs
))
726 buf_size
-= sizeof(struct album_data
);
728 if ( album_count
> 0 )
729 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
732 /* not enough memory */
733 return ERROR_BUFFER_FULL
;
735 rb
->strcpy(buf
, tcs
.result
);
737 buf
= l
+ (char *)buf
;
738 album
[-album_count
].seek
= tcs
.result_seek
;
742 rb
->tagcache_search_finish(&tcs
);
743 ALIGN_BUFFER(buf
, buf_size
, 4);
745 struct album_data
* tmp_album
= (struct album_data
*)buf
;
746 for (i
= album_count
- 1; i
>= 0; i
--)
747 tmp_album
[i
] = album
[-i
];
749 buf
= album
+ album_count
;
750 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
754 Return a pointer to the album name of the given slide_index
756 char* get_album_name(const int slide_index
)
758 return album_names
+ album
[slide_index
].name_idx
;
762 Return a pointer to the track name of the active album
763 create_track_index has to be called first.
765 char* get_track_name(const int track_index
)
767 if ( track_index
< track_count
)
768 return track_names
+ tracks
[track_index
].name_idx
;
771 #if PF_PLAYBACK_CAPABLE
772 char* get_track_filename(const int track_index
)
774 if ( track_index
< track_count
)
775 return track_names
+ tracks
[track_index
].filename_idx
;
780 Compare two unsigned ints passed via pointers.
782 int compare_tracks (const void *a_v
, const void *b_v
)
784 uint32_t a
= ((struct track_data
*)a_v
)->sort
;
785 uint32_t b
= ((struct track_data
*)b_v
)->sort
;
790 Create the track index of the given slide_index.
792 void create_track_index(const int slide_index
)
794 if ( slide_index
== track_index
)
796 track_index
= slide_index
;
798 if (!rb
->tagcache_search(&tcs
, tag_title
))
801 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
803 int string_index
= 0, track_num
;
806 track_names
= (char *)buflib_buffer_out(&buf_ctx
, &out
);
808 int avail
= borrowed
;
809 tracks
= (struct track_data
*)(track_names
+ borrowed
);
810 while (rb
->tagcache_get_next(&tcs
))
812 int len
= 0, fn_idx
= 0;
814 avail
-= sizeof(struct track_data
);
815 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
) - 1;
816 disc_num
= rb
->tagcache_get_numeric(&tcs
, tag_discnumber
);
824 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
825 "%d.%02d: %s", disc_num
, track_num
+ 1, tcs
.result
);
827 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
828 "%d: %s", track_num
+ 1, tcs
.result
);
833 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
838 #if PF_PLAYBACK_CAPABLE
839 int remain
= avail
- fn_idx
;
840 if (remain
>= MAX_PATH
)
841 { /* retrieve filename for building the playlist */
842 rb
->tagcache_retrieve(&tcs
, tcs
.idx_id
, tag_filename
,
843 track_names
+ string_index
+ fn_idx
, remain
);
844 len
= fn_idx
+ rb
->strlen(track_names
+ string_index
+ fn_idx
) + 1;
845 /* make sure track name and file name are really split by a \0, else
846 * get_track_name might fail */
847 *(track_names
+ string_index
+ fn_idx
-1) = '\0';
850 else /* request more buffer so that track and filename fit */
851 len
= (avail
- remain
) + MAX_PATH
;
859 if (!free_slide_prio(0))
862 buflib_buffer_out(&buf_ctx
, &out
);
867 struct track_data
*new_tracks
= (struct track_data
*)(out
+ (uintptr_t)tracks
);
868 unsigned int bytes
= track_count
* sizeof(struct track_data
);
869 rb
->memmove(new_tracks
, tracks
, bytes
);
878 tracks
->sort
= ((disc_num
- 1) << 24) + (track_num
<< 14) + track_count
;
879 tracks
->name_idx
= string_index
;
880 tracks
->seek
= tcs
.result_seek
;
881 #if PF_PLAYBACK_CAPABLE
882 tracks
->filename_idx
= fn_idx
+ string_index
;
888 rb
->tagcache_search_finish(&tcs
);
890 /* now fix the track list order */
891 rb
->qsort(tracks
, track_count
, sizeof(struct track_data
), compare_tracks
);
899 Determine filename of the album art for the given slide_index and
900 store the result in buf.
901 The algorithm looks for the first track of the given album uses
902 find_albumart to find the filename.
904 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
907 if ( slide_index
== -1 )
909 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
912 if (!rb
->tagcache_search(&tcs
, tag_filename
))
916 /* find the first track of the album */
917 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
919 if ( rb
->tagcache_get_next(&tcs
) ) {
923 #ifdef HAVE_TC_RAMCACHE
924 if (rb
->tagcache_fill_tags(&id3
, tcs
.result
))
926 rb
->strncpy(id3
.path
, tcs
.result
, sizeof(id3
.path
));
927 id3
.path
[sizeof(id3
.path
) - 1] = 0;
932 fd
= rb
->open(tcs
.result
, O_RDONLY
);
933 rb
->get_metadata(&id3
, fd
, tcs
.result
);
936 if ( search_albumart_files(&id3
, ":", buf
, buflen
) )
942 /* did not find a matching track */
945 rb
->tagcache_search_finish(&tcs
);
950 Draw the PictureFlow logo
952 void draw_splashscreen(void)
954 unsigned char * buf_tmp
= buf
;
955 size_t buf_tmp_size
= buf_size
;
956 struct screen
* display
= rb
->screens
[0];
958 ALIGN_BUFFER(buf_tmp
, buf_tmp_size
, sizeof(fb_data
));
960 struct bitmap logo
= {
970 int ret
= rb
->read_bmp_file(SPLASH_BMP
, &logo
, buf_tmp_size
, FORMAT_NATIVE
,
973 rb
->lcd_set_background(N_BRIGHT(0));
974 rb
->lcd_set_foreground(N_BRIGHT(255));
976 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
978 rb
->lcd_clear_display();
982 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
983 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
985 display
->bitmap(logo
.data
, (LCD_WIDTH
- logo
.width
) / 2, 10,
986 logo
.width
, logo
.height
);
987 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
988 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
997 Draw a simple progress bar
999 void draw_progressbar(int step
)
1002 const int bar_height
= 22;
1003 const int w
= LCD_WIDTH
- 20;
1006 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
1008 int y
= (LCD_HEIGHT
- txt_h
)/2;
1010 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
1014 rb
->lcd_set_foreground(N_BRIGHT(100));
1016 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
1018 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
1021 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
1023 rb
->lcd_set_foreground(N_BRIGHT(255));
1030 Precomupte the album art images and store them in CACHE_PREFIX.
1032 bool create_albumart_cache(void)
1037 struct bitmap input_bmp
;
1039 char pfraw_file
[MAX_PATH
];
1040 char albumart_file
[MAX_PATH
];
1041 unsigned int format
= FORMAT_NATIVE
;
1043 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
1045 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
1046 for (i
=0; i
< album_count
; i
++)
1048 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
1050 /* delete existing cache, so it's a true rebuild */
1051 if(rb
->file_exists(pfraw_file
))
1052 rb
->remove(pfraw_file
);
1053 draw_progressbar(i
);
1054 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
1057 input_bmp
.data
= buf
;
1058 input_bmp
.width
= DISPLAY_WIDTH
;
1059 input_bmp
.height
= DISPLAY_HEIGHT
;
1060 ret
= read_image_file(albumart_file
, &input_bmp
,
1061 buf_size
, format
, &format_transposed
);
1063 rb
->splash(HZ
, "Could not read bmp");
1064 continue; /* skip missing/broken files */
1066 if (!save_pfraw(pfraw_file
, &input_bmp
))
1068 rb
->splash(HZ
, "Could not write bmp");
1071 if ( rb
->button_get(false) == PF_MENU
) return false;
1073 if ( slides
== 0 ) {
1074 /* Warn the user that we couldn't find any albumart */
1075 rb
->splash(2*HZ
, "No album art found");
1082 Thread used for loading and preparing bitmaps in the background
1086 long sleep_time
= 5 * HZ
;
1087 struct queue_event ev
;
1089 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1094 /* we just woke up */
1097 while ( load_new_slide() ) {
1109 End the thread by posting the EV_EXIT event
1111 void end_pf_thread(void)
1113 if ( thread_is_running
) {
1114 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
1115 rb
->thread_wait(thread_id
);
1116 /* remove the thread's queue from the broadcast list */
1117 rb
->queue_delete(&thread_q
);
1118 thread_is_running
= false;
1125 Create the thread an setup the event queue
1127 bool create_pf_thread(void)
1129 /* put the thread's queue in the bcast list */
1130 rb
->queue_init(&thread_q
, true);
1131 if ((thread_id
= rb
->create_thread(
1134 sizeof(thread_stack
),
1136 "Picture load thread"
1137 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1138 PRIORITY_REALTIME
+ 1))
1144 thread_is_running
= true;
1145 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1150 Safe the given bitmap as filename in the pfraw format
1152 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1154 struct pfraw_header bmph
;
1155 bmph
.width
= bm
->width
;
1156 bmph
.height
= bm
->height
;
1157 int fh
= rb
->creat( filename
);
1158 if( fh
< 0 ) return false;
1159 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1161 for( y
= 0; y
< bm
->height
; y
++ )
1163 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
1164 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
1172 * The following functions implement the linked-list-in-array used to manage
1173 * the LRU cache of slides, and the list of free cache slots.
1176 #define seek_right_while(start, cond) \
1178 int ind_, next_ = (start); \
1181 next_ = cache[ind_].next; \
1182 } while (next_ != cache_used && (cond)); \
1186 #define seek_left_while(start, cond) \
1188 int ind_, next_ = (start); \
1191 next_ = cache[ind_].prev; \
1192 } while (ind_ != cache_used && (cond)); \
1197 Pop the given item from the linked list starting at *head, returning the next
1198 item, or -1 if the list is now empty.
1200 static inline int lla_pop_item (int *head
, int i
)
1202 int prev
= cache
[i
].prev
;
1203 int next
= cache
[i
].next
;
1209 else if (i
== *head
)
1211 cache
[next
].prev
= prev
;
1212 cache
[prev
].next
= next
;
1218 Pop the head item from the list starting at *head, returning the index of the
1219 item, or -1 if the list is already empty.
1221 static inline int lla_pop_head (int *head
)
1225 lla_pop_item(head
, i
);
1230 Insert the item at index i before the one at index p.
1232 static inline void lla_insert (int i
, int p
)
1235 int prev
= cache
[next
].prev
;
1236 cache
[next
].prev
= i
;
1237 cache
[prev
].next
= i
;
1238 cache
[i
].next
= next
;
1239 cache
[i
].prev
= prev
;
1244 Insert the item at index i at the end of the list starting at *head.
1246 static inline void lla_insert_tail (int *head
, int i
)
1254 lla_insert(i
, *head
);
1258 Insert the item at index i before the one at index p.
1260 static inline void lla_insert_after(int i
, int p
)
1268 Insert the item at index i before the one at index p in the list starting at
1271 static inline void lla_insert_before(int *head
, int i
, int p
)
1280 Free the used slide at index i, and its buffer, and move it to the free
1283 static inline void free_slide(int i
)
1285 if (cache
[i
].hid
!= empty_slide_hid
)
1286 buflib_free(&buf_ctx
, cache
[i
].hid
);
1287 cache
[i
].index
= -1;
1288 lla_pop_item(&cache_used
, i
);
1289 lla_insert_tail(&cache_free
, i
);
1290 if (cache_used
== -1)
1292 cache_right_index
= -1;
1293 cache_left_index
= -1;
1294 cache_center_index
= -1;
1300 Free one slide ranked above the given priority. If no such slide can be found,
1303 static bool free_slide_prio(int prio
)
1305 if (cache_used
== -1)
1307 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1308 int prio_l
= cache
[l
].index
< center_index
?
1309 center_index
- cache
[l
].index
: 0;
1310 int prio_r
= cache
[r
].index
> center_index
?
1311 cache
[r
].index
- center_index
: 0;
1312 if (prio_l
> prio_r
)
1320 if (prio_max
> prio
)
1322 if (i
== cache_left_index
)
1323 cache_left_index
= cache
[i
].next
;
1324 if (i
== cache_right_index
)
1325 cache_right_index
= cache
[i
].prev
;
1333 Read the pfraw image given as filename and return the hid of the buffer
1335 int read_pfraw(char* filename
, int prio
)
1337 struct pfraw_header bmph
;
1338 int fh
= rb
->open(filename
, O_RDONLY
);
1340 return empty_slide_hid
;
1342 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1344 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1345 bmph
.width
* bmph
.height
;
1348 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1355 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1357 bm
->width
= bmph
.width
;
1358 bm
->height
= bmph
.height
;
1359 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1362 for( y
= 0; y
< bm
->height
; y
++ )
1364 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1373 Load the surface for the given slide_index into the cache at cache_index.
1375 static inline bool load_and_prepare_surface(const int slide_index
,
1376 const int cache_index
,
1379 char tmp_path_name
[MAX_PATH
+1];
1380 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1383 int hid
= read_pfraw(tmp_path_name
, prio
);
1387 cache
[cache_index
].hid
= hid
;
1389 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1390 cache
[cache_index
].index
= slide_index
;
1398 Load the "next" slide that we can load, freeing old slides if needed, provided
1399 that they are further from center_index than the current slide
1401 bool load_new_slide(void)
1404 if (cache_center_index
!= -1)
1407 if (cache
[cache_center_index
].index
!= center_index
)
1409 if (cache
[cache_center_index
].index
< center_index
)
1411 cache_center_index
= seek_right_while(cache_center_index
,
1412 cache
[next_
].index
<= center_index
);
1413 prev
= cache_center_index
;
1414 next
= cache
[cache_center_index
].next
;
1418 cache_center_index
= seek_left_while(cache_center_index
,
1419 cache
[next_
].index
>= center_index
);
1420 next
= cache_center_index
;
1421 prev
= cache
[cache_center_index
].prev
;
1423 if (cache
[cache_center_index
].index
!= center_index
)
1425 if (cache_free
== -1)
1427 i
= lla_pop_head(&cache_free
);
1428 if (!load_and_prepare_surface(center_index
, i
, 0))
1429 goto fail_and_refree
;
1430 if (cache
[next
].index
== -1)
1432 if (cache
[prev
].index
== -1)
1433 goto insert_first_slide
;
1435 next
= cache
[prev
].next
;
1437 lla_insert(i
, next
);
1438 if (cache
[i
].index
< cache
[cache_used
].index
)
1440 cache_center_index
= i
;
1441 cache_left_index
= i
;
1442 cache_right_index
= i
;
1446 if (cache
[cache_left_index
].index
>
1447 cache
[cache_center_index
].index
)
1448 cache_left_index
= cache_center_index
;
1449 if (cache
[cache_right_index
].index
<
1450 cache
[cache_center_index
].index
)
1451 cache_right_index
= cache_center_index
;
1452 cache_left_index
= seek_left_while(cache_left_index
,
1453 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1454 cache_right_index
= seek_right_while(cache_right_index
,
1455 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1456 int prio_l
= cache
[cache_center_index
].index
-
1457 cache
[cache_left_index
].index
+ 1;
1458 int prio_r
= cache
[cache_right_index
].index
-
1459 cache
[cache_center_index
].index
+ 1;
1460 if ((prio_l
< prio_r
||
1461 cache
[cache_right_index
].index
>= number_of_slides
) &&
1462 cache
[cache_left_index
].index
> 0)
1464 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1466 i
= lla_pop_head(&cache_free
);
1467 if (load_and_prepare_surface(cache
[cache_left_index
].index
1470 lla_insert_before(&cache_used
, i
, cache_left_index
);
1471 cache_left_index
= i
;
1474 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1476 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1478 i
= lla_pop_head(&cache_free
);
1479 if (load_and_prepare_surface(cache
[cache_right_index
].index
1482 lla_insert_after(i
, cache_right_index
);
1483 cache_right_index
= i
;
1488 i
= lla_pop_head(&cache_free
);
1489 if (load_and_prepare_surface(center_index
, i
, 0))
1494 cache_center_index
= i
;
1495 cache_left_index
= i
;
1496 cache_right_index
= i
;
1504 lla_insert_tail(&cache_free
, i
);
1511 Get a slide from the buffer
1513 static inline struct dim
*get_slide(const int hid
)
1520 bmp
= buflib_get_data(&buf_ctx
, hid
);
1527 Return the requested surface
1529 static inline struct dim
*surface(const int slide_index
)
1531 if (slide_index
< 0)
1533 if (slide_index
>= number_of_slides
)
1536 if ((i
= cache_used
) != -1)
1539 if (cache
[i
].index
== slide_index
)
1540 return get_slide(cache
[i
].hid
);
1542 } while (i
!= cache_used
);
1544 return get_slide(empty_slide_hid
);
1548 adjust slides so that they are in "steady state" position
1550 void reset_slides(void)
1552 center_slide
.angle
= 0;
1553 center_slide
.cx
= 0;
1554 center_slide
.cy
= 0;
1555 center_slide
.distance
= 0;
1556 center_slide
.slide_index
= center_index
;
1559 for (i
= 0; i
< num_slides
; i
++) {
1560 struct slide_data
*si
= &left_slides
[i
];
1562 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1564 si
->slide_index
= center_index
- 1 - i
;
1568 for (i
= 0; i
< num_slides
; i
++) {
1569 struct slide_data
*si
= &right_slides
[i
];
1571 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1573 si
->slide_index
= center_index
+ 1 + i
;
1580 Updates look-up table and other stuff necessary for the rendering.
1581 Call this when the viewport size or slide dimension is changed.
1583 * To calculate the offset that will provide the proper margin, we use the same
1584 * projection used to render the slides. The solution for xc, the slide center,
1586 * xp * (zo + xs * sin(r))
1587 * xc = xp - xs * cos(r) + ───────────────────────
1589 * TODO: support moving the side slides toward or away from the camera
1591 void recalc_offsets(void)
1593 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1595 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1596 PFREAL_ONE
) * zoom
/ 100;
1599 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1600 cosr
= fcos(-itilt
);
1601 sinr
= fsin(-itilt
);
1602 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1603 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1604 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1605 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1607 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1612 Fade the given color by spreading the fb_data (ushort)
1613 to an uint, multiply and compress the result back to a ushort.
1615 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1616 static inline unsigned fade_color(pix_t c
, unsigned a
)
1618 unsigned int result
;
1620 a
= (a
+ 2) & 0x1fc;
1621 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1622 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1624 return swap16(result
);
1626 #elif LCD_PIXELFORMAT == RGB565
1627 static inline unsigned fade_color(pix_t c
, unsigned a
)
1629 unsigned int result
;
1630 a
= (a
+ 2) & 0x1fc;
1631 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1632 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1637 static inline unsigned fade_color(pix_t c
, unsigned a
)
1640 return MULUQ(val
, a
) >> 8;
1645 * Render a single slide
1646 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1647 * on the slide from its center, zo is the slide's depth offset from the plane
1648 * of the display, r is the angle at which the slide is tilted, and xp is the
1649 * point on the display corresponding to xs on the slide, the projection
1652 * z * (xc + xs * cos(r))
1653 * xp = ──────────────────────
1654 * z + zo + xs * sin(r)
1656 * z * (xc - xp) - xp * zo
1657 * xs = ────────────────────────
1658 * xp * sin(r) - z * cos(r)
1660 * We use the xp projection once, to find the left edge of the slide on the
1661 * display. From there, we use the xs reverse projection to find the horizontal
1662 * offset from the slide center of each column on the screen, until we reach
1663 * the right edge of the slide, or the screen. The reverse projection can be
1664 * optimized by saving the numerator and denominator of the fraction, which can
1665 * then be incremented by (z + zo) and sin(r) respectively.
1667 void render_slide(struct slide_data
*slide
, const int alpha
)
1669 struct dim
*bmp
= surface(slide
->slide_index
);
1673 if (slide
->angle
> 255 || slide
->angle
< -255)
1675 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1677 const int sw
= bmp
->width
;
1678 const int sh
= bmp
->height
;
1679 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1680 const int w
= LCD_WIDTH
;
1682 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1684 if (alpha
== 256) { /* opaque -> copy table */
1685 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1686 } else { /* precalculate faded table */
1688 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1689 lalpha
= reflect_table
[i
];
1690 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1694 PFreal cosr
= fcos(slide
->angle
);
1695 PFreal sinr
= fsin(slide
->angle
);
1696 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1697 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1698 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1699 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1700 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1702 /* Since we're finding the screen position of the left edge of the slide,
1705 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1707 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1711 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1712 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1713 xs
= fdiv(xsnum
, xsden
);
1715 xsnumi
= -CAM_DIST_R
- zo
;
1718 int dy
= PFREAL_ONE
;
1719 for (x
= xi
; x
< w
; x
++) {
1720 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1723 if (zo
|| slide
->angle
)
1724 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1726 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1727 const int pixelstep
= BUFFER_WIDTH
;
1729 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1730 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1731 pix_t
*pixel
= &buffer
[((LCD_HEIGHT
/2)-1)*BUFFER_WIDTH
+ x
];
1735 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1741 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1746 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1747 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1748 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1749 p
+ (LCD_HEIGHT
/2) * dy
);
1750 pixel
= &buffer
[(LCD_HEIGHT
/2)*BUFFER_WIDTH
+ x
];
1754 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1760 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1766 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1767 int lalpha
= reftab
[ty
];
1768 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1773 if (zo
|| slide
->angle
)
1777 xs
= fdiv(xsnum
, xsden
);
1782 /* let the music play... */
1789 Jump the the given slide_index
1791 static inline void set_current_slide(const int slide_index
)
1793 int old_center_index
= center_index
;
1795 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1796 if (old_center_index
!= center_index
)
1797 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1798 target
= center_index
;
1799 slide_frame
= slide_index
<< 16;
1804 Start the animation for changing slides
1806 void start_animation(void)
1808 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1809 pf_state
= pf_scrolling
;
1813 Go to the previous slide
1815 void show_previous_slide(void)
1818 if (center_index
> 0) {
1819 target
= center_index
- 1;
1822 } else if ( step
> 0 ) {
1823 target
= center_index
;
1826 target
= fmax(0, center_index
- 2);
1832 Go to the next slide
1834 void show_next_slide(void)
1837 if (center_index
< number_of_slides
- 1) {
1838 target
= center_index
+ 1;
1841 } else if ( step
< 0 ) {
1842 target
= center_index
;
1845 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1851 Render the slides. Updates only the offscreen buffer.
1853 void render_all_slides(void)
1855 MYLCD(set_background
)(G_BRIGHT(0));
1856 /* TODO: Optimizes this by e.g. invalidating rects */
1857 MYLCD(clear_display
)();
1859 int nleft
= num_slides
;
1860 int nright
= num_slides
;
1864 /* no animation, boring plain rendering */
1865 for (index
= nleft
- 2; index
>= 0; index
--) {
1866 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1867 alpha
-= extra_fade
;
1869 render_slide(&left_slides
[index
], alpha
);
1871 for (index
= nright
- 2; index
>= 0; index
--) {
1872 int alpha
= (index
< nright
- 2) ? 256 : 128;
1873 alpha
-= extra_fade
;
1875 render_slide(&right_slides
[index
], alpha
);
1878 /* the first and last slide must fade in/fade out */
1879 for (index
= nleft
- 1; index
>= 0; index
--) {
1881 if (index
== nleft
- 1)
1882 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1883 if (index
== nleft
- 2)
1884 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1885 if (index
== nleft
- 3)
1886 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1887 render_slide(&left_slides
[index
], alpha
);
1889 for (index
= nright
- 1; index
>= 0; index
--) {
1890 int alpha
= (index
< nright
- 2) ? 256 : 128;
1891 if (index
== nright
- 1)
1892 alpha
= (step
> 0) ? fade
/ 2 : 0;
1893 if (index
== nright
- 2)
1894 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1895 if (index
== nright
- 3)
1896 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1897 render_slide(&right_slides
[index
], alpha
);
1900 render_slide(¢er_slide
, 256);
1905 Updates the animation effect. Call this periodically from a timer.
1907 void update_scroll_animation(void)
1915 /* deaccelerate when approaching the target */
1917 const int max
= 2 * 65536;
1919 int fi
= slide_frame
;
1920 fi
-= (target
<< 16);
1925 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1926 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1929 slide_frame
+= speed
* step
;
1931 int index
= slide_frame
>> 16;
1932 int pos
= slide_frame
& 0xffff;
1933 int neg
= 65536 - pos
;
1934 int tick
= (step
< 0) ? neg
: pos
;
1935 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1937 /* the leftmost and rightmost slide must fade away */
1942 if (center_index
!= index
) {
1943 center_index
= index
;
1944 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1945 slide_frame
= index
<< 16;
1946 center_slide
.slide_index
= center_index
;
1947 for (i
= 0; i
< num_slides
; i
++)
1948 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1949 for (i
= 0; i
< num_slides
; i
++)
1950 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1953 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1954 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1955 center_slide
.cy
= fmul(offsetY
, ftick
);
1957 if (center_index
== target
) {
1965 for (i
= 0; i
< num_slides
; i
++) {
1966 struct slide_data
*si
= &left_slides
[i
];
1969 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1970 * slide_spacing
* ftick
);
1974 for (i
= 0; i
< num_slides
; i
++) {
1975 struct slide_data
*si
= &right_slides
[i
];
1978 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1979 * slide_spacing
* ftick
;
1984 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1985 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1986 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1987 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1989 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1990 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1991 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1992 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1995 /* must change direction ? */
2008 void cleanup(void *parameter
)
2011 /* Turn on backlight timeout (revert to settings) */
2012 backlight_use_settings(); /* backlight control in lib/helper.c */
2020 Create the "?" slide, that is shown while loading
2021 or when no cover was found.
2023 int create_empty_slide(bool force
)
2025 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
2026 struct bitmap input_bmp
;
2028 input_bmp
.width
= DISPLAY_WIDTH
;
2029 input_bmp
.height
= DISPLAY_HEIGHT
;
2031 input_bmp
.format
= FORMAT_NATIVE
;
2033 input_bmp
.data
= (char*)buf
;
2034 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
2036 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
2037 &format_transposed
);
2038 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
2046 Shows the album name setting menu
2048 int album_name_menu(void)
2050 int selection
= show_album_name
;
2052 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
2053 "Hide album title", "Show at the bottom", "Show at the top");
2054 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
2056 show_album_name
= selection
;
2057 return GO_TO_PREVIOUS
;
2061 Shows the settings menu
2063 int settings_menu(void)
2068 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
2069 "Spacing", "Centre margin", "Number of slides", "Zoom",
2070 "Show album title", "Resize Covers", "Rebuild cache");
2073 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2076 rb
->set_bool("Show FPS", &show_fps
);
2081 rb
->set_int("Spacing between slides", "", 1,
2083 NULL
, 1, 0, 100, NULL
);
2089 rb
->set_int("Centre margin", "", 1,
2091 NULL
, 1, 0, 80, NULL
);
2097 rb
->set_int("Number of slides", "", 1, &num_slides
,
2098 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2104 rb
->set_int("Zoom", "", 1, &zoom
,
2105 NULL
, 1, 10, 300, NULL
);
2117 rb
->set_bool("Resize Covers", &resize
);
2118 if (old_val
== resize
) /* changed? */
2120 /* fallthrough if changed, since cache needs to be rebuilt */
2123 rb
->remove(EMPTY_SLIDE
);
2124 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2127 case MENU_ATTACHED_USB
:
2128 return PLUGIN_USB_CONNECTED
;
2130 } while ( selection
>= 0 );
2131 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2139 #if PF_PLAYBACK_CAPABLE
2140 PF_MENU_PLAYBACK_CONTROl
,
2153 rb
->lcd_set_foreground(N_BRIGHT(255));
2156 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2157 #if PF_PLAYBACK_CAPABLE
2160 "Settings", "Return", "Quit");
2162 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2163 #if PF_PLAYBACK_CAPABLE
2164 case PF_MENU_PLAYBACK_CONTROl
: /* Playback Control */
2165 playback_control(NULL
);
2168 case PF_MENU_SETTINGS
:
2169 result
= settings_menu();
2170 if ( result
!= 0 ) return result
;
2173 case PF_MENU_RETURN
:
2179 case MENU_ATTACHED_USB
:
2180 return PLUGIN_USB_CONNECTED
;
2189 Animation step for zooming into the current cover
2191 void update_cover_in_animation(void)
2193 cover_animation_keyframe
++;
2194 if( cover_animation_keyframe
< 20 ) {
2195 center_slide
.distance
-=5;
2196 center_slide
.angle
+=1;
2199 else if( cover_animation_keyframe
< 35 ) {
2200 center_slide
.angle
+=16;
2203 cover_animation_keyframe
= 0;
2204 pf_state
= pf_show_tracks
;
2209 Animation step for zooming out the current cover
2211 void update_cover_out_animation(void)
2213 cover_animation_keyframe
++;
2214 if( cover_animation_keyframe
<= 15 ) {
2215 center_slide
.angle
-=16;
2217 else if( cover_animation_keyframe
< 35 ) {
2218 center_slide
.distance
+=5;
2219 center_slide
.angle
-=1;
2223 cover_animation_keyframe
= 0;
2229 Draw a blue gradient at y with height h
2231 static inline void draw_gradient(int y
, int h
)
2233 static int r
, inc
, c
;
2234 inc
= (100 << 8) / h
;
2236 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2237 int c2
= selected_track_pulse
- 5;
2238 for (r
=0; r
<h
; r
++) {
2239 #ifdef HAVE_LCD_COLOR
2240 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2243 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2245 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2254 static void track_list_yh(int char_height
)
2256 switch (show_album_name
)
2258 case album_name_hide
:
2259 track_list_y
= (show_fps
? char_height
: 0);
2260 track_list_h
= LCD_HEIGHT
- track_list_y
;
2262 case album_name_bottom
:
2263 track_list_y
= (show_fps
? char_height
: 0);
2264 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2266 default: /* case album_name_top */
2267 track_list_y
= char_height
* 2;
2268 track_list_h
= LCD_HEIGHT
- track_list_y
-
2269 (show_fps
? char_height
: 0);
2275 Reset the track list after a album change
2277 void reset_track_list(void)
2279 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2280 track_list_yh(albumtxt_h
);
2281 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2282 start_index_track_list
= 0;
2283 track_scroll_index
= 0;
2284 track_scroll_dir
= 1;
2287 /* let the tracklist start more centered
2288 * if the screen isn't filled with tracks */
2289 if (track_count
*albumtxt_h
< track_list_h
)
2291 track_list_h
= track_count
* albumtxt_h
;
2292 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2297 Display the list of tracks
2299 void show_track_list(void)
2301 MYLCD(clear_display
)();
2302 if ( center_slide
.slide_index
!= track_index
) {
2303 create_track_index(center_slide
.slide_index
);
2306 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2307 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2309 int titletxt_y
= track_list_y
;
2311 track_i
= start_index_track_list
;
2312 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2315 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2316 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2317 if ( track_i
== selected_track
) {
2318 draw_gradient(titletxt_y
, titletxt_h
);
2319 MYLCD(set_foreground
)(G_BRIGHT(255));
2320 if (titletxt_w
> LCD_WIDTH
) {
2321 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2322 track_scroll_dir
= 1;
2323 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2324 track_scroll_index
+= track_scroll_dir
*2;
2325 titletxt_x
= track_scroll_index
;
2327 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2330 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2331 MYLCD(set_foreground
)(G_BRIGHT(color
));
2332 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2334 titletxt_y
+= titletxt_h
;
2338 void select_next_track(void)
2340 if ( selected_track
< track_count
- 1 ) {
2342 track_scroll_index
= 0;
2343 track_scroll_dir
= 1;
2344 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2345 start_index_track_list
++;
2349 void select_prev_track(void)
2351 if (selected_track
> 0 ) {
2352 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2353 track_scroll_index
= 0;
2354 track_scroll_dir
= 1;
2359 #if PF_PLAYBACK_CAPABLE
2361 * Puts the current tracklist into a newly created playlist and starts playling
2363 void start_playback(void)
2365 static int old_playlist
= -1, old_shuffle
= 0;
2367 int position
= selected_track
;
2368 int shuffle
= rb
->global_settings
->playlist_shuffle
;
2369 /* reuse existing playlist if possible
2370 * regenerate if shuffle is on or changed, since playlist index and
2371 * selected track are "out of sync" */
2372 if (!shuffle
&& center_slide
.slide_index
== old_playlist
2373 && (old_shuffle
== shuffle
))
2377 /* First, replace the current playlist with a new one */
2378 else if (rb
->playlist_remove_all_tracks(NULL
) == 0
2379 && rb
->playlist_create(NULL
, NULL
) == 0)
2383 if (rb
->playlist_insert_track(NULL
, get_track_filename(count
),
2384 PLAYLIST_INSERT_LAST
, false, true) < 0)
2386 } while(++count
< track_count
);
2387 rb
->playlist_sync(NULL
);
2392 if (rb
->global_settings
->playlist_shuffle
)
2393 position
= rb
->playlist_shuffle(*rb
->current_tick
, selected_track
);
2395 /* TODO: can we adjust selected_track if !play_selected ?
2396 * if shuffle, we can't predict the playing track easily, and for either
2397 * case the track list doesn't get auto scrolled*/
2398 rb
->playlist_start(position
, 0);
2399 old_playlist
= center_slide
.slide_index
;
2400 old_shuffle
= shuffle
;
2404 Draw the current album name
2406 void draw_album_text(void)
2408 if (0 == show_album_name
)
2411 int albumtxt_w
, albumtxt_h
;
2416 /* Draw album text */
2417 if ( pf_state
== pf_scrolling
) {
2418 c
= ((slide_frame
& 0xffff )/ 255);
2419 if (step
< 0) c
= 255-c
;
2420 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2421 albumtxt
= get_album_name(center_index
+step
);
2425 albumtxt
= get_album_name(center_index
);
2431 albumtxt
= get_album_name(center_index
);
2434 MYLCD(set_foreground
)(G_BRIGHT(c
));
2435 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2436 if (center_index
!= prev_center_index
) {
2439 prev_center_index
= center_index
;
2442 if (show_album_name
== album_name_top
)
2443 albumtxt_y
= albumtxt_h
/ 2;
2445 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2447 if (albumtxt_w
> LCD_WIDTH
) {
2448 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2449 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2450 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2451 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2452 albumtxt_x
+= albumtxt_dir
;
2456 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2463 Display an error message and wait for input.
2465 void error_wait(const char *message
)
2467 rb
->splashf(0, "%s. Press any button to continue.", message
);
2468 while (rb
->get_action(CONTEXT_STD
, 1) == ACTION_NONE
)
2474 Main function that also contain the main plasma
2481 rb
->lcd_setfont(FONT_UI
);
2482 draw_splashscreen();
2484 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2485 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2486 error_wait("Could not create directory " CACHE_PREFIX
);
2487 return PLUGIN_ERROR
;
2491 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2493 init_reflect_table();
2495 ALIGN_BUFFER(buf
, buf_size
, 4);
2496 ret
= create_album_index();
2497 if (ret
== ERROR_BUFFER_FULL
) {
2498 error_wait("Not enough memory for album names");
2499 return PLUGIN_ERROR
;
2500 } else if (ret
== ERROR_NO_ALBUMS
) {
2501 error_wait("No albums found. Please enable database");
2502 return PLUGIN_ERROR
;
2505 ALIGN_BUFFER(buf
, buf_size
, 4);
2506 number_of_slides
= album_count
;
2507 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2508 error_wait("Could not create album art cache");
2509 return PLUGIN_ERROR
;
2512 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2513 error_wait("Could not load the empty slide");
2514 return PLUGIN_ERROR
;
2516 cache_version
= CACHE_VERSION
;
2517 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2522 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2523 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2525 error_wait("Greylib init failed!");
2526 return PLUGIN_ERROR
;
2528 grey_setfont(FONT_UI
);
2529 buf_size
-= grey_buf_used
;
2530 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2532 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2534 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2536 error_wait("Unable to load empty slide image");
2537 return PLUGIN_ERROR
;
2540 if (!create_pf_thread()) {
2541 error_wait("Cannot create thread!");
2542 return PLUGIN_ERROR
;
2548 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2551 cache
[i
].next
= i
+ 1;
2552 cache
[i
].prev
= i
- 1;
2554 cache
[0].prev
= i
- 1;
2555 cache
[i
- 1].next
= 0;
2575 long last_update
= *rb
->current_tick
;
2576 long current_update
;
2577 long update_interval
= 100;
2581 bool instant_update
;
2584 grey_set_drawmode(DRMODE_FG
);
2586 rb
->lcd_set_drawmode(DRMODE_FG
);
2588 current_update
= *rb
->current_tick
;
2591 /* Initial rendering */
2592 instant_update
= false;
2595 switch ( pf_state
) {
2597 update_scroll_animation();
2598 render_all_slides();
2599 instant_update
= true;
2602 update_cover_in_animation();
2603 render_all_slides();
2604 instant_update
= true;
2607 update_cover_out_animation();
2608 render_all_slides();
2609 instant_update
= true;
2611 case pf_show_tracks
:
2615 render_all_slides();
2620 if (current_update
- last_update
> update_interval
) {
2621 fps
= frames
* HZ
/ (current_update
- last_update
);
2622 last_update
= current_update
;
2629 MYLCD(set_foreground
)(G_BRIGHT(255));
2631 MYLCD(set_foreground
)(G_PIX(255,0,0));
2633 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2634 if (show_album_name
== album_name_top
)
2635 fpstxt_y
= LCD_HEIGHT
-
2636 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2639 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2644 /* Copy offscreen buffer to LCD and give time to other threads */
2648 /*/ Handle buttons */
2649 button
= rb
->get_custom_action(CONTEXT_PLUGIN
2650 #if !defined(HAVE_SCROLLWHEEL)
2651 |(pf_state
== pf_show_tracks
? 1 : 0)
2653 ,instant_update
? 0 : HZ
/16,
2661 if ( pf_state
== pf_show_tracks
)
2663 buflib_buffer_in(&buf_ctx
, borrowed
);
2666 pf_state
= pf_cover_out
;
2668 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2677 if ( ret
== -1 ) return PLUGIN_OK
;
2678 if ( ret
!= 0 ) return ret
;
2682 MYLCD(set_drawmode
)(DRMODE_FG
);
2686 case PF_NEXT_REPEAT
:
2687 if ( pf_state
== pf_show_tracks
)
2688 select_next_track();
2689 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2694 case PF_PREV_REPEAT
:
2695 if ( pf_state
== pf_show_tracks
)
2696 select_prev_track();
2697 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2698 show_previous_slide();
2702 if ( pf_state
== pf_idle
) {
2703 pf_state
= pf_cover_in
;
2705 else if ( pf_state
== pf_show_tracks
) {
2706 #if PF_PLAYBACK_CAPABLE
2713 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2714 == SYS_USB_CONNECTED
)
2715 return PLUGIN_USB_CONNECTED
;
2723 /*************************** Plugin entry point ****************************/
2725 enum plugin_status
plugin_start(const void *parameter
)
2730 rb
->lcd_set_backdrop(NULL
);
2732 /* Turn off backlight timeout */
2733 backlight_force_on(); /* backlight control in lib/helper.c */
2734 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2735 rb
->cpu_boost(true);
2737 #if PF_PLAYBACK_CAPABLE
2738 buf
= rb
->plugin_get_buffer(&buf_size
);
2740 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2742 if ((uintptr_t)buf
< (uintptr_t)plugin_start_addr
)
2744 uint32_t tmp_size
= (uintptr_t)plugin_start_addr
- (uintptr_t)buf
;
2745 buf_size
= MIN(buf_size
, tmp_size
);
2750 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2751 rb
->cpu_boost(false);
2753 if ( ret
== PLUGIN_OK
) {
2754 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2757 rb
->splash(HZ
, "Error writing config.");