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"
32 #include "lib/picture.h"
33 #include "pluginbitmaps/pictureflow_logo.h"
35 #include "lib/feature_wrappers.h"
36 #include "lib/buflib.h"
40 /******************************* Globals ***********************************/
42 #define PF_PREV ACTION_STD_PREV
43 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
44 #define PF_NEXT ACTION_STD_NEXT
45 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
46 #define PF_SELECT ACTION_STD_OK
47 #define PF_CONTEXT ACTION_STD_CONTEXT
48 #define PF_BACK ACTION_STD_CANCEL
49 #define PF_MENU ACTION_STD_MENU
50 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
52 const struct button_mapping pf_context_album_scroll
[] =
54 #ifdef HAVE_TOUCHSCREEN
55 {PF_PREV
, BUTTON_MIDLEFT
, BUTTON_NONE
},
56 {PF_PREV_REPEAT
, BUTTON_MIDLEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
57 {PF_NEXT
, BUTTON_MIDRIGHT
, BUTTON_NONE
},
58 {PF_NEXT_REPEAT
, BUTTON_MIDRIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
60 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
61 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
62 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
63 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
64 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
65 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
66 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
67 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
68 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
69 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
70 CONFIG_KEYPAD == MEIZU_M6SL_PAD
71 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
72 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
73 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
74 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
75 #elif CONFIG_KEYPAD == ONDIO_PAD
76 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
77 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
78 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
79 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
80 {PF_SELECT
, BUTTON_UP
|BUTTON_REL
, BUTTON_UP
},
81 {PF_CONTEXT
, BUTTON_UP
|BUTTON_REPEAT
, BUTTON_UP
},
82 {ACTION_NONE
, BUTTON_UP
, BUTTON_NONE
},
83 {ACTION_NONE
, BUTTON_DOWN
, BUTTON_NONE
},
84 {ACTION_NONE
, BUTTON_DOWN
|BUTTON_REPEAT
, BUTTON_NONE
},
85 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REL
, BUTTON_RIGHT
},
86 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
87 {PF_PREV
, BUTTON_RC_REW
, BUTTON_NONE
},
88 {PF_PREV_REPEAT
, BUTTON_RC_REW
|BUTTON_REPEAT
,BUTTON_NONE
},
89 {PF_NEXT
, BUTTON_RC_FF
, BUTTON_NONE
},
90 {PF_NEXT_REPEAT
, BUTTON_RC_FF
|BUTTON_REPEAT
, BUTTON_NONE
},
92 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM
|1)
95 const struct button_mapping pf_context_buttons
[] =
97 #ifdef HAVE_TOUCHSCREEN
98 {PF_SELECT
, BUTTON_CENTER
, BUTTON_NONE
},
99 {PF_MENU
, BUTTON_TOPLEFT
, BUTTON_NONE
},
100 {PF_BACK
, BUTTON_BOTTOMRIGHT
, BUTTON_NONE
},
102 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
103 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
104 #elif CONFIG_KEYPAD == SANSA_C100_PAD
105 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
106 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
107 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
108 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
109 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
110 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
111 CONFIG_KEYPAD == SANSA_FUZE_PAD
112 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
113 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
115 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
116 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
117 {PF_QUIT
, BUTTON_POWER
|BUTTON_REPEAT
, BUTTON_POWER
},
118 #if CONFIG_KEYPAD == COWOND2_PAD
119 {PF_BACK
, BUTTON_POWER
|BUTTON_REL
, BUTTON_POWER
},
120 {ACTION_NONE
, BUTTON_POWER
, BUTTON_NONE
},
122 #elif CONFIG_KEYPAD == SANSA_E200_PAD
123 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
124 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
125 {PF_QUIT
, BUTTON_EQ
, BUTTON_NONE
},
126 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
127 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
128 || (CONFIG_KEYPAD == IPOD_4G_PAD)
129 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
130 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
131 {PF_QUIT
, BUTTON_POWERPLAY
|BUTTON_REPEAT
, BUTTON_POWERPLAY
},
132 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
133 {PF_QUIT
, BUTTON_RC_REC
, BUTTON_NONE
},
134 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
135 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
136 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
137 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
138 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
140 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
|CONTEXT_REMOTE
)
143 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
)
146 const struct button_mapping
*pf_contexts
[] =
148 pf_context_album_scroll
,
154 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
155 #else /* LCD_DEPTH <= 1 */
156 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
157 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
158 #define PICTUREFLOW_DRMODE DRMODE_SOLID
160 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
162 #endif /* LCD_DEPTH <= 1 */
165 #define LCD_BUF _grey_info.buffer
166 #define MYLCD(fn) grey_ ## fn
167 #define G_PIX(r,g,b) \
168 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
169 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
170 #define G_BRIGHT(y) (y)
171 #define BUFFER_WIDTH _grey_info.width
172 #define BUFFER_HEIGHT _grey_info.height
173 typedef unsigned char pix_t
;
174 #else /* LCD_DEPTH >= 8 */
175 #define LCD_BUF rb->lcd_framebuffer
176 #define MYLCD(fn) rb->lcd_ ## fn
177 #define G_PIX LCD_RGBPACK
178 #define N_PIX LCD_RGBPACK
179 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
180 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
181 #define BUFFER_WIDTH LCD_WIDTH
182 #define BUFFER_HEIGHT LCD_HEIGHT
183 typedef fb_data pix_t
;
184 #endif /* LCD_DEPTH >= 8 */
186 /* for fixed-point arithmetic, we need minimum 32-bit long
187 long long (64-bit) might be useful for multiplication and division */
189 #define PFREAL_SHIFT 10
190 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
191 #define PFREAL_ONE (1 << PFREAL_SHIFT)
192 #define PFREAL_HALF (PFREAL_ONE >> 1)
195 #define IANGLE_MAX 1024
196 #define IANGLE_MASK 1023
198 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
199 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
200 #define DISPLAY_HEIGHT REFLECT_TOP
201 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
202 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
203 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
204 (REFLECT_HEIGHT * 5))
205 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
206 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
207 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
208 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
209 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
211 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
213 #define MAX_SLIDES_COUNT 10
215 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
216 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
219 #define EV_WAKEUP 1337
221 /* maximum number of albums */
223 #define MAX_TRACKS 50
224 #define AVG_TRACK_NAME_LENGTH 20
227 #define UNIQBUF_SIZE (64*1024)
229 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
230 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
232 /* Error return values */
233 #define ERROR_NO_ALBUMS -1
234 #define ERROR_BUFFER_FULL -2
236 /* current version for cover cache */
237 #define CACHE_VERSION 2
238 #define CONFIG_VERSION 1
239 #define CONFIG_FILE "pictureflow.cfg"
241 /** structs we use */
252 int index
; /* index of the cached slide */
253 int hid
; /* handle ID of the cached slide */
254 short next
; /* "next" slide, with LRU last */
255 short prev
; /* "previous" slide */
275 struct load_slide_event_data
{
281 struct pfraw_header
{
282 int32_t width
; /* bmap width in pixels */
283 int32_t height
; /* bmap height in pixels */
286 const struct picture logos
[]={
287 {pictureflow_logo
, BMPWIDTH_pictureflow_logo
, BMPHEIGHT_pictureflow_logo
},
290 enum show_album_name_values
{ album_name_hide
= 0, album_name_bottom
,
292 static char* show_album_name_conf
[] =
299 #define MAX_SPACING 40
300 #define MAX_MARGIN 80
302 /* config values and their defaults */
303 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
304 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
305 static int num_slides
= 4;
306 static int zoom
= 100;
307 static bool show_fps
= false;
308 static bool resize
= true;
309 static int cache_version
= 0;
310 static int show_album_name
= (LCD_HEIGHT
> 100)
311 ? album_name_top
: album_name_bottom
;
313 static struct configdata config
[] =
315 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
317 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
319 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
321 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
322 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
323 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
324 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
325 { TYPE_ENUM
, 0, 2, { .int_p
= &show_album_name
}, "show album name",
326 show_album_name_conf
}
329 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
331 /** below we allocate the memory we want to use **/
333 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
334 static uint8_t reflect_table
[REFLECT_HEIGHT
];
335 static struct slide_data center_slide
;
336 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
337 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
338 static int slide_frame
;
342 static int center_index
= 0; /* index of the slide that is in the center */
344 static PFreal offsetX
;
345 static PFreal offsetY
;
346 static int number_of_slides
;
348 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
349 static int cache_free
;
350 static int cache_used
= -1;
351 static int cache_left_index
= -1;
352 static int cache_right_index
= -1;
353 static int cache_center_index
= -1;
355 /* use long for aligning */
356 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
357 /* queue (as array) for scheduling load_surface */
359 static int empty_slide_hid
;
361 unsigned int thread_id
;
362 struct event_queue thread_q
;
364 static struct tagcache_search tcs
;
366 static struct buflib_context buf_ctx
;
368 static struct album_data
*album
;
369 static char *album_names
;
370 static int album_count
;
372 static char track_names
[MAX_TRACKS
* AVG_TRACK_NAME_LENGTH
];
373 static struct track_data tracks
[MAX_TRACKS
];
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 inline unsigned fade_color(pix_t c
, unsigned a
);
427 bool save_pfraw(char* filename
, struct bitmap
*bm
);
428 bool load_new_slide(void);
429 int load_surface(int);
431 static inline PFreal
fmul(PFreal a
, PFreal b
)
433 return (a
*b
) >> PFREAL_SHIFT
;
437 * This version preshifts each operand, which is useful when we know how many
438 * of the least significant bits will be empty, or are worried about overflow
439 * in a particular calculation
441 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
443 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
446 /* ARMv5+ has a clz instruction equivalent to our function.
448 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
449 static inline int clz(uint32_t v
)
451 return __builtin_clz(v
);
454 /* Otherwise, use our clz, which can be inlined */
455 #elif defined(CPU_COLDFIRE)
456 /* This clz is based on the log2(n) implementation at
457 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
458 * A clz benchmark plugin showed this to be about 14% faster on coldfire
459 * than the LUT-based version.
461 static inline int clz(uint32_t v
)
493 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
494 0, 0, 0, 0, 0, 0, 0, 0 };
495 /* This clz is based on the log2(n) implementation at
496 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
497 * It is not any faster than the one above, but trades 16B in the lookup table
498 * for a savings of 12B per each inlined call.
500 static inline int clz(uint32_t v
)
518 return r
+ clz_lut
[v
];
522 /* Return the maximum possible left shift for a signed int32, without
525 static inline int allowed_shift(int32_t val
)
527 uint32_t uval
= val
^ (val
>> 31);
528 return clz(uval
) - 1;
531 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
532 * num and den before dividing.
534 static inline PFreal
fdiv(PFreal num
, PFreal den
)
536 int shift
= allowed_shift(num
);
537 shift
= MIN(PFREAL_SHIFT
, shift
);
539 den
>>= PFREAL_SHIFT
- shift
;
543 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
544 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
545 #define fabs(a) (a < 0 ? -a : a)
546 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
548 #if CONFIG_CPU == SH7034
549 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
550 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
552 #define MULUQ(a, b) ((a) * (b))
557 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
558 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
560 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
561 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
563 static inline PFreal
fmul(PFreal a
, PFreal b
)
565 return (a
*b
) >> PFREAL_SHIFT
;
568 static inline PFreal
fdiv(PFreal n
, PFreal m
)
570 return (n
<<(PFREAL_SHIFT
))/m
;
574 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
575 static const short sin_tab
[] = {
576 0, 100, 200, 297, 392, 483, 569, 650,
577 724, 792, 851, 903, 946, 980, 1004, 1019,
578 1024, 1019, 1004, 980, 946, 903, 851, 792,
579 724, 650, 569, 483, 392, 297, 200, 100,
580 0, -100, -200, -297, -392, -483, -569, -650,
581 -724, -792, -851, -903, -946, -980, -1004, -1019,
582 -1024, -1019, -1004, -980, -946, -903, -851, -792,
583 -724, -650, -569, -483, -392, -297, -200, -100,
587 static inline PFreal
fsin(int iangle
)
589 iangle
&= IANGLE_MASK
;
591 int i
= (iangle
>> 4);
592 PFreal p
= sin_tab
[i
];
593 PFreal q
= sin_tab
[(i
+1)];
595 return p
+ g
* (iangle
-i
*16)/16;
598 static inline PFreal
fcos(int iangle
)
600 return fsin(iangle
+ (IANGLE_MAX
>> 2));
603 static inline uint32_t div255(uint32_t val
)
605 return ((((val
>> 8) + val
) >> 8) + val
) >> 8;
608 #define SCALE_VAL(val,out) div255((val) * (out) + 127)
610 static void output_row_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 uint32_t *qp
= (uint32_t*)row_in
;
617 for (; dest
< end
; dest
+= ctx
->bm
->height
)
618 *dest
= SC_MUL((*qp
++) + ctx
->round
, ctx
->divisor
);
620 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
621 uint32_t rb_mul
= SCALE_VAL(ctx
->divisor
, 31),
622 rb_rnd
= SCALE_VAL(ctx
->round
, 31),
623 g_mul
= SCALE_VAL(ctx
->divisor
, 63),
624 g_rnd
= SCALE_VAL(ctx
->round
, 63);
626 for (; dest
< end
; dest
+= ctx
->bm
->height
)
628 r
= SC_MUL(qp
->r
+ rb_rnd
, rb_mul
);
629 g
= SC_MUL(qp
->g
+ g_rnd
, g_mul
);
630 b
= SC_MUL(qp
->b
+ rb_rnd
, rb_mul
);
632 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
637 static unsigned int get_size(struct bitmap
*bm
)
639 return bm
->width
* bm
->height
* sizeof(pix_t
);
642 const struct custom_format format_transposed
= {
643 .output_row
= output_row_transposed
,
647 static const struct button_mapping
* get_context_map(int context
)
649 return pf_contexts
[context
& ~CONTEXT_CUSTOM
];
652 /* Create the lookup table with the scaling values for the reflections */
653 void init_reflect_table(void)
656 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
658 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
659 (5 * REFLECT_HEIGHT
);
663 Create an index of all albums from the database.
664 Also store the album names so we can access them later.
666 int create_album_index(void)
668 buf_size
-= UNIQBUF_SIZE
* sizeof(long);
669 long *uniqbuf
= (long *)(buf_size
+ (char *)buf
);
670 album
= ((struct album_data
*)uniqbuf
) - 1;
671 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
673 rb
->tagcache_search(&tcs
, tag_album
);
674 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
675 unsigned int l
, old_l
= 0;
677 album
[0].name_idx
= 0;
678 while (rb
->tagcache_get_next(&tcs
))
680 buf_size
-= sizeof(struct album_data
);
681 l
= rb
->strlen(tcs
.result
) + 1;
682 if ( album_count
> 0 )
683 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
686 /* not enough memory */
687 return ERROR_BUFFER_FULL
;
689 rb
->strcpy(buf
, tcs
.result
);
691 buf
= l
+ (char *)buf
;
692 album
[-album_count
].seek
= tcs
.result_seek
;
696 rb
->tagcache_search_finish(&tcs
);
697 ALIGN_BUFFER(buf
, buf_size
, 4);
699 struct album_data
* tmp_album
= (struct album_data
*)buf
;
700 for (i
= album_count
- 1; i
>= 0; i
--)
701 tmp_album
[i
] = album
[-i
];
703 buf
= album
+ album_count
;
704 buf_size
+= UNIQBUF_SIZE
* sizeof(long);
705 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
709 Return a pointer to the album name of the given slide_index
711 char* get_album_name(const int slide_index
)
713 return album_names
+ album
[slide_index
].name_idx
;
717 Return a pointer to the track name of the active album
718 create_track_index has to be called first.
720 char* get_track_name(const int track_index
)
722 if ( track_index
< track_count
)
723 return track_names
+ tracks
[track_index
].name_idx
;
728 Create the track index of the given slide_index.
730 int create_track_index(const int slide_index
)
732 if ( slide_index
== track_index
) {
736 if (!rb
->tagcache_search(&tcs
, tag_title
))
740 char temp_titles
[MAX_TRACKS
][AVG_TRACK_NAME_LENGTH
*4];
741 int temp_seeks
[MAX_TRACKS
];
743 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
745 int string_index
= 0;
746 int l
, track_num
, heighest_index
= 0;
748 for(l
=0;l
<MAX_TRACKS
;l
++)
749 temp_titles
[l
][0] = '\0';
750 while (rb
->tagcache_get_next(&tcs
) && track_count
< MAX_TRACKS
)
752 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
) - 1;
755 rb
->snprintf(temp_titles
[track_num
],sizeof(temp_titles
[track_num
]),
756 "%d: %s", track_num
+1, tcs
.result
);
757 temp_seeks
[track_num
] = tcs
.result_seek
;
762 while (temp_titles
[track_num
][0] != '\0')
764 rb
->strcpy(temp_titles
[track_num
], tcs
.result
);
765 temp_seeks
[track_num
] = tcs
.result_seek
;
767 if (track_num
> heighest_index
)
768 heighest_index
= track_num
;
772 rb
->tagcache_search_finish(&tcs
);
773 track_index
= slide_index
;
775 /* now fix the track list order */
778 while (l
<= heighest_index
&&
779 string_index
< MAX_TRACKS
*AVG_TRACK_NAME_LENGTH
)
781 if (temp_titles
[l
][0] != '\0')
783 rb
->strcpy(track_names
+ string_index
, temp_titles
[l
]);
784 tracks
[track_count
].name_idx
= string_index
;
785 tracks
[track_count
].seek
= temp_seeks
[l
];
786 string_index
+= rb
->strlen(temp_titles
[l
]) + 1;
794 return (track_count
> 0) ? 0 : -1;
798 Determine filename of the album art for the given slide_index and
799 store the result in buf.
800 The algorithm looks for the first track of the given album uses
801 find_albumart to find the filename.
803 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
806 if ( slide_index
== -1 )
808 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
811 if (!rb
->tagcache_search(&tcs
, tag_filename
))
815 /* find the first track of the album */
816 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
818 if ( rb
->tagcache_get_next(&tcs
) ) {
822 fd
= rb
->open(tcs
.result
, O_RDONLY
);
823 rb
->get_metadata(&id3
, fd
, tcs
.result
);
825 if ( search_albumart_files(&id3
, "", buf
, buflen
) )
831 /* did not find a matching track */
834 rb
->tagcache_search_finish(&tcs
);
839 Draw the PictureFlow logo
841 void draw_splashscreen(void)
843 struct screen
* display
= rb
->screens
[0];
844 const struct picture
* logo
= &(logos
[display
->screen_type
]);
847 rb
->lcd_set_background(N_BRIGHT(0));
848 rb
->lcd_set_foreground(N_BRIGHT(255));
850 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
852 rb
->lcd_clear_display();
854 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
855 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
856 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
857 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
859 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
867 Draw a simple progress bar
869 void draw_progressbar(int step
)
872 const int bar_height
= 22;
873 const int w
= LCD_WIDTH
- 20;
876 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
878 int y
= (LCD_HEIGHT
- txt_h
)/2;
880 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
884 rb
->lcd_set_foreground(N_BRIGHT(100));
886 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
888 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
891 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
893 rb
->lcd_set_foreground(N_BRIGHT(255));
900 Precomupte the album art images and store them in CACHE_PREFIX.
902 bool create_albumart_cache(void)
907 struct bitmap input_bmp
;
909 char pfraw_file
[MAX_PATH
];
910 char albumart_file
[MAX_PATH
];
911 unsigned int format
= FORMAT_NATIVE
;
913 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
915 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
916 for (i
=0; i
< album_count
; i
++)
918 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
920 /* delete existing cache, so it's a true rebuild */
921 if(rb
->file_exists(pfraw_file
))
922 rb
->remove(pfraw_file
);
924 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
927 input_bmp
.data
= buf
;
928 input_bmp
.width
= DISPLAY_WIDTH
;
929 input_bmp
.height
= DISPLAY_HEIGHT
;
930 #if PLUGIN_BUFFER_SIZE > 0x10000
931 ret
= read_image_file(albumart_file
, &input_bmp
,
932 buf_size
, format
, &format_transposed
);
934 ret
= scaled_read_bmp_file(albumart_file
, &input_bmp
,
935 buf_size
, format
, &format_transposed
);
938 rb
->splash(HZ
, "Could not read bmp");
939 continue; /* skip missing/broken files */
941 if (!save_pfraw(pfraw_file
, &input_bmp
))
943 rb
->splash(HZ
, "Could not write bmp");
946 if ( rb
->button_get(false) == PF_MENU
) return false;
949 /* Warn the user that we couldn't find any albumart */
950 rb
->splash(2*HZ
, "No album art found");
957 Thread used for loading and preparing bitmaps in the background
961 long sleep_time
= 5 * HZ
;
962 struct queue_event ev
;
964 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
969 /* we just woke up */
972 while ( load_new_slide() ) {
984 End the thread by posting the EV_EXIT event
986 void end_pf_thread(void)
988 if ( thread_is_running
) {
989 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
990 rb
->thread_wait(thread_id
);
991 /* remove the thread's queue from the broadcast list */
992 rb
->queue_delete(&thread_q
);
993 thread_is_running
= false;
1000 Create the thread an setup the event queue
1002 bool create_pf_thread(void)
1004 /* put the thread's queue in the bcast list */
1005 rb
->queue_init(&thread_q
, true);
1006 if ((thread_id
= rb
->create_thread(
1009 sizeof(thread_stack
),
1011 "Picture load thread"
1012 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1013 PRIORITY_REALTIME
+ 1))
1019 thread_is_running
= true;
1020 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1025 Safe the given bitmap as filename in the pfraw format
1027 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1029 struct pfraw_header bmph
;
1030 bmph
.width
= bm
->width
;
1031 bmph
.height
= bm
->height
;
1032 int fh
= rb
->creat( filename
);
1033 if( fh
< 0 ) return false;
1034 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1036 for( y
= 0; y
< bm
->height
; y
++ )
1038 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
1039 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
1047 * The following functions implement the linked-list-in-array used to manage
1048 * the LRU cache of slides, and the list of free cache slots.
1051 #define seek_right_while(start, cond) \
1053 int ind_, next_ = (start); \
1056 next_ = cache[ind_].next; \
1057 } while (next_ != cache_used && (cond)); \
1061 #define seek_left_while(start, cond) \
1063 int ind_, next_ = (start); \
1066 next_ = cache[ind_].prev; \
1067 } while (ind_ != cache_used && (cond)); \
1072 Pop the given item from the linked list starting at *head, returning the next
1073 item, or -1 if the list is now empty.
1075 static inline int lla_pop_item (int *head
, int i
)
1077 int prev
= cache
[i
].prev
;
1078 int next
= cache
[i
].next
;
1084 else if (i
== *head
)
1086 cache
[next
].prev
= prev
;
1087 cache
[prev
].next
= next
;
1093 Pop the head item from the list starting at *head, returning the index of the
1094 item, or -1 if the list is already empty.
1096 static inline int lla_pop_head (int *head
)
1100 lla_pop_item(head
, i
);
1105 Insert the item at index i before the one at index p.
1107 static inline void lla_insert (int i
, int p
)
1110 int prev
= cache
[next
].prev
;
1111 cache
[next
].prev
= i
;
1112 cache
[prev
].next
= i
;
1113 cache
[i
].next
= next
;
1114 cache
[i
].prev
= prev
;
1119 Insert the item at index i at the end of the list starting at *head.
1121 static inline void lla_insert_tail (int *head
, int i
)
1129 lla_insert(i
, *head
);
1133 Insert the item at index i before the one at index p.
1135 static inline void lla_insert_after(int i
, int p
)
1143 Insert the item at index i before the one at index p in the list starting at
1146 static inline void lla_insert_before(int *head
, int i
, int p
)
1155 Free the used slide at index i, and its buffer, and move it to the free
1158 static inline void free_slide(int i
)
1160 if (cache
[i
].hid
!= empty_slide_hid
)
1161 buflib_free(&buf_ctx
, cache
[i
].hid
);
1162 cache
[i
].index
= -1;
1163 lla_pop_item(&cache_used
, i
);
1164 lla_insert_tail(&cache_free
, i
);
1165 if (cache_used
== -1)
1167 cache_right_index
= -1;
1168 cache_left_index
= -1;
1169 cache_center_index
= -1;
1175 Free one slide ranked above the given priority. If no such slide can be found,
1178 static inline int free_slide_prio(int prio
)
1180 if (cache_used
== -1)
1182 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1183 int prio_l
= cache
[l
].index
< center_index
?
1184 center_index
- cache
[l
].index
: 0;
1185 int prio_r
= cache
[r
].index
> center_index
?
1186 cache
[r
].index
- center_index
: 0;
1187 if (prio_l
> prio_r
)
1195 if (prio_max
> prio
)
1197 if (i
== cache_left_index
)
1198 cache_left_index
= cache
[i
].next
;
1199 if (i
== cache_right_index
)
1200 cache_right_index
= cache
[i
].prev
;
1208 Read the pfraw image given as filename and return the hid of the buffer
1210 int read_pfraw(char* filename
, int prio
)
1212 struct pfraw_header bmph
;
1213 int fh
= rb
->open(filename
, O_RDONLY
);
1215 return empty_slide_hid
;
1217 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1219 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1220 bmph
.width
* bmph
.height
;
1223 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1230 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1232 bm
->width
= bmph
.width
;
1233 bm
->height
= bmph
.height
;
1234 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1237 for( y
= 0; y
< bm
->height
; y
++ )
1239 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1248 Load the surface for the given slide_index into the cache at cache_index.
1250 static inline bool load_and_prepare_surface(const int slide_index
,
1251 const int cache_index
,
1254 char tmp_path_name
[MAX_PATH
+1];
1255 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1258 int hid
= read_pfraw(tmp_path_name
, prio
);
1262 cache
[cache_index
].hid
= hid
;
1264 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1265 cache
[cache_index
].index
= slide_index
;
1273 Load the "next" slide that we can load, freeing old slides if needed, provided
1274 that they are further from center_index than the current slide
1276 bool load_new_slide(void)
1279 if (cache_center_index
!= -1)
1282 if (cache
[cache_center_index
].index
!= center_index
)
1284 if (cache
[cache_center_index
].index
< center_index
)
1286 cache_center_index
= seek_right_while(cache_center_index
,
1287 cache
[next_
].index
<= center_index
);
1288 prev
= cache_center_index
;
1289 next
= cache
[cache_center_index
].next
;
1293 cache_center_index
= seek_left_while(cache_center_index
,
1294 cache
[next_
].index
>= center_index
);
1295 next
= cache_center_index
;
1296 prev
= cache
[cache_center_index
].prev
;
1298 if (cache
[cache_center_index
].index
!= center_index
)
1300 if (cache_free
== -1)
1302 i
= lla_pop_head(&cache_free
);
1303 if (!load_and_prepare_surface(center_index
, i
, 0))
1304 goto fail_and_refree
;
1305 if (cache
[next
].index
== -1)
1307 if (cache
[prev
].index
== -1)
1308 goto insert_first_slide
;
1310 next
= cache
[prev
].next
;
1312 lla_insert(i
, next
);
1313 if (cache
[i
].index
< cache
[cache_used
].index
)
1315 cache_center_index
= i
;
1316 cache_left_index
= i
;
1317 cache_right_index
= i
;
1321 if (cache
[cache_left_index
].index
>
1322 cache
[cache_center_index
].index
)
1323 cache_left_index
= cache_center_index
;
1324 if (cache
[cache_right_index
].index
<
1325 cache
[cache_center_index
].index
)
1326 cache_right_index
= cache_center_index
;
1327 cache_left_index
= seek_left_while(cache_left_index
,
1328 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1329 cache_right_index
= seek_right_while(cache_right_index
,
1330 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1331 int prio_l
= cache
[cache_center_index
].index
-
1332 cache
[cache_left_index
].index
+ 1;
1333 int prio_r
= cache
[cache_right_index
].index
-
1334 cache
[cache_center_index
].index
+ 1;
1335 if ((prio_l
< prio_r
||
1336 cache
[cache_right_index
].index
>= number_of_slides
) &&
1337 cache
[cache_left_index
].index
> 0)
1339 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1341 i
= lla_pop_head(&cache_free
);
1342 if (load_and_prepare_surface(cache
[cache_left_index
].index
1345 lla_insert_before(&cache_used
, i
, cache_left_index
);
1346 cache_left_index
= i
;
1349 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1351 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1353 i
= lla_pop_head(&cache_free
);
1354 if (load_and_prepare_surface(cache
[cache_right_index
].index
1357 lla_insert_after(i
, cache_right_index
);
1358 cache_right_index
= i
;
1363 i
= lla_pop_head(&cache_free
);
1364 if (load_and_prepare_surface(center_index
, i
, 0))
1369 cache_center_index
= i
;
1370 cache_left_index
= i
;
1371 cache_right_index
= i
;
1379 lla_insert_tail(&cache_free
, i
);
1386 Get a slide from the buffer
1388 static inline struct dim
*get_slide(const int hid
)
1395 bmp
= buflib_get_data(&buf_ctx
, hid
);
1402 Return the requested surface
1404 static inline struct dim
*surface(const int slide_index
)
1406 if (slide_index
< 0)
1408 if (slide_index
>= number_of_slides
)
1411 if ((i
= cache_used
) != -1)
1414 if (cache
[i
].index
== slide_index
)
1415 return get_slide(cache
[i
].hid
);
1417 } while (i
!= cache_used
);
1419 return get_slide(empty_slide_hid
);
1423 adjust slides so that they are in "steady state" position
1425 void reset_slides(void)
1427 center_slide
.angle
= 0;
1428 center_slide
.cx
= 0;
1429 center_slide
.cy
= 0;
1430 center_slide
.distance
= 0;
1431 center_slide
.slide_index
= center_index
;
1434 for (i
= 0; i
< num_slides
; i
++) {
1435 struct slide_data
*si
= &left_slides
[i
];
1437 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1439 si
->slide_index
= center_index
- 1 - i
;
1443 for (i
= 0; i
< num_slides
; i
++) {
1444 struct slide_data
*si
= &right_slides
[i
];
1446 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1448 si
->slide_index
= center_index
+ 1 + i
;
1455 Updates look-up table and other stuff necessary for the rendering.
1456 Call this when the viewport size or slide dimension is changed.
1458 * To calculate the offset that will provide the proper margin, we use the same
1459 * projection used to render the slides. The solution for xc, the slide center,
1461 * xp * (zo + xs * sin(r))
1462 * xc = xp - xs * cos(r) + ───────────────────────
1464 * TODO: support moving the side slides toward or away from the camera
1466 void recalc_offsets(void)
1468 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1470 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1471 PFREAL_ONE
) * zoom
/ 100;
1474 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1475 cosr
= fcos(-itilt
);
1476 sinr
= fsin(-itilt
);
1477 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1478 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1479 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1480 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1482 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1487 Fade the given color by spreading the fb_data (ushort)
1488 to an uint, multiply and compress the result back to a ushort.
1490 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1491 static inline unsigned fade_color(pix_t c
, unsigned a
)
1493 unsigned int result
;
1495 a
= (a
+ 2) & 0x1fc;
1496 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1497 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1499 return swap16(result
);
1501 #elif LCD_PIXELFORMAT == RGB565
1502 static inline unsigned fade_color(pix_t c
, unsigned a
)
1504 unsigned int result
;
1505 a
= (a
+ 2) & 0x1fc;
1506 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1507 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1512 static inline unsigned fade_color(pix_t c
, unsigned a
)
1515 return MULUQ(val
, a
) >> 8;
1520 * Render a single slide
1521 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1522 * on the slide from its center, zo is the slide's depth offset from the plane
1523 * of the display, r is the angle at which the slide is tilted, and xp is the
1524 * point on the display corresponding to xs on the slide, the projection
1527 * z * (xc + xs * cos(r))
1528 * xp = ──────────────────────
1529 * z + zo + xs * sin(r)
1531 * z * (xc - xp) - xp * zo
1532 * xs = ────────────────────────
1533 * xp * sin(r) - z * cos(r)
1535 * We use the xp projection once, to find the left edge of the slide on the
1536 * display. From there, we use the xs reverse projection to find the horizontal
1537 * offset from the slide center of each column on the screen, until we reach
1538 * the right edge of the slide, or the screen. The reverse projection can be
1539 * optimized by saving the numerator and denominator of the fraction, which can
1540 * then be incremented by (z + zo) and sin(r) respectively.
1542 void render_slide(struct slide_data
*slide
, const int alpha
)
1544 struct dim
*bmp
= surface(slide
->slide_index
);
1548 if (slide
->angle
> 255 || slide
->angle
< -255)
1550 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1552 const int sw
= bmp
->width
;
1553 const int sh
= bmp
->height
;
1554 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1555 const int w
= LCD_WIDTH
;
1557 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1559 if (alpha
== 256) { /* opaque -> copy table */
1560 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1561 } else { /* precalculate faded table */
1563 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1564 lalpha
= reflect_table
[i
];
1565 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1569 PFreal cosr
= fcos(slide
->angle
);
1570 PFreal sinr
= fsin(slide
->angle
);
1571 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1572 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1573 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1574 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1575 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1577 /* Since we're finding the screen position of the left edge of the slide,
1580 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1582 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1586 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1587 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1588 xs
= fdiv(xsnum
, xsden
);
1590 xsnumi
= -CAM_DIST_R
- zo
;
1593 int dy
= PFREAL_ONE
;
1594 for (x
= xi
; x
< w
; x
++) {
1595 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1598 if (zo
|| slide
->angle
)
1599 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1601 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1602 const int pixelstep
= BUFFER_WIDTH
;
1604 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1605 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1606 pix_t
*pixel
= &buffer
[((LCD_HEIGHT
/2)-1)*BUFFER_WIDTH
+ x
];
1610 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1616 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1621 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1622 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1623 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1624 p
+ (LCD_HEIGHT
/2) * dy
);
1625 pixel
= &buffer
[(LCD_HEIGHT
/2)*BUFFER_WIDTH
+ x
];
1629 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1635 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1641 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1642 int lalpha
= reftab
[ty
];
1643 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1648 if (zo
|| slide
->angle
)
1652 xs
= fdiv(xsnum
, xsden
);
1657 /* let the music play... */
1664 Jump the the given slide_index
1666 static inline void set_current_slide(const int slide_index
)
1668 int old_center_index
= center_index
;
1670 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1671 if (old_center_index
!= center_index
)
1672 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1673 target
= center_index
;
1674 slide_frame
= slide_index
<< 16;
1679 Start the animation for changing slides
1681 void start_animation(void)
1683 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1684 pf_state
= pf_scrolling
;
1688 Go to the previous slide
1690 void show_previous_slide(void)
1693 if (center_index
> 0) {
1694 target
= center_index
- 1;
1697 } else if ( step
> 0 ) {
1698 target
= center_index
;
1701 target
= fmax(0, center_index
- 2);
1707 Go to the next slide
1709 void show_next_slide(void)
1712 if (center_index
< number_of_slides
- 1) {
1713 target
= center_index
+ 1;
1716 } else if ( step
< 0 ) {
1717 target
= center_index
;
1720 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1726 Render the slides. Updates only the offscreen buffer.
1728 void render_all_slides(void)
1730 MYLCD(set_background
)(G_BRIGHT(0));
1731 /* TODO: Optimizes this by e.g. invalidating rects */
1732 MYLCD(clear_display
)();
1734 int nleft
= num_slides
;
1735 int nright
= num_slides
;
1739 /* no animation, boring plain rendering */
1740 for (index
= nleft
- 2; index
>= 0; index
--) {
1741 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1742 alpha
-= extra_fade
;
1744 render_slide(&left_slides
[index
], alpha
);
1746 for (index
= nright
- 2; index
>= 0; index
--) {
1747 int alpha
= (index
< nright
- 2) ? 256 : 128;
1748 alpha
-= extra_fade
;
1750 render_slide(&right_slides
[index
], alpha
);
1753 /* the first and last slide must fade in/fade out */
1754 for (index
= nleft
- 1; index
>= 0; index
--) {
1756 if (index
== nleft
- 1)
1757 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1758 if (index
== nleft
- 2)
1759 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1760 if (index
== nleft
- 3)
1761 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1762 render_slide(&left_slides
[index
], alpha
);
1764 for (index
= nright
- 1; index
>= 0; index
--) {
1765 int alpha
= (index
< nright
- 2) ? 256 : 128;
1766 if (index
== nright
- 1)
1767 alpha
= (step
> 0) ? fade
/ 2 : 0;
1768 if (index
== nright
- 2)
1769 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1770 if (index
== nright
- 3)
1771 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1772 render_slide(&right_slides
[index
], alpha
);
1775 render_slide(¢er_slide
, 256);
1780 Updates the animation effect. Call this periodically from a timer.
1782 void update_scroll_animation(void)
1790 /* deaccelerate when approaching the target */
1792 const int max
= 2 * 65536;
1794 int fi
= slide_frame
;
1795 fi
-= (target
<< 16);
1800 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1801 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1804 slide_frame
+= speed
* step
;
1806 int index
= slide_frame
>> 16;
1807 int pos
= slide_frame
& 0xffff;
1808 int neg
= 65536 - pos
;
1809 int tick
= (step
< 0) ? neg
: pos
;
1810 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1812 /* the leftmost and rightmost slide must fade away */
1817 if (center_index
!= index
) {
1818 center_index
= index
;
1819 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1820 slide_frame
= index
<< 16;
1821 center_slide
.slide_index
= center_index
;
1822 for (i
= 0; i
< num_slides
; i
++)
1823 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1824 for (i
= 0; i
< num_slides
; i
++)
1825 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1828 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1829 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1830 center_slide
.cy
= fmul(offsetY
, ftick
);
1832 if (center_index
== target
) {
1840 for (i
= 0; i
< num_slides
; i
++) {
1841 struct slide_data
*si
= &left_slides
[i
];
1844 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1845 * slide_spacing
* ftick
);
1849 for (i
= 0; i
< num_slides
; i
++) {
1850 struct slide_data
*si
= &right_slides
[i
];
1853 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1854 * slide_spacing
* ftick
;
1859 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1860 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1861 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1862 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1864 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1865 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1866 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1867 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1870 /* must change direction ? */
1883 void cleanup(void *parameter
)
1886 /* Turn on backlight timeout (revert to settings) */
1887 backlight_use_settings(); /* backlight control in lib/helper.c */
1895 Create the "?" slide, that is shown while loading
1896 or when no cover was found.
1898 int create_empty_slide(bool force
)
1900 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1901 struct bitmap input_bmp
;
1903 input_bmp
.width
= DISPLAY_WIDTH
;
1904 input_bmp
.height
= DISPLAY_HEIGHT
;
1906 input_bmp
.format
= FORMAT_NATIVE
;
1908 input_bmp
.data
= (char*)buf
;
1909 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1911 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1912 &format_transposed
);
1913 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1921 Shows the album name setting menu
1923 int album_name_menu(void)
1925 int selection
= show_album_name
;
1927 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
1928 "Hide album title", "Show at the bottom", "Show at the top");
1929 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
1931 show_album_name
= selection
;
1932 return GO_TO_PREVIOUS
;
1936 Shows the settings menu
1938 int settings_menu(void)
1943 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1944 "Spacing", "Centre margin", "Number of slides", "Zoom",
1945 "Show album title", "Resize Covers", "Rebuild cache");
1948 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1951 rb
->set_bool("Show FPS", &show_fps
);
1956 rb
->set_int("Spacing between slides", "", 1,
1958 NULL
, 1, 0, 100, NULL
);
1964 rb
->set_int("Centre margin", "", 1,
1966 NULL
, 1, 0, 80, NULL
);
1972 rb
->set_int("Number of slides", "", 1, &num_slides
,
1973 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
1979 rb
->set_int("Zoom", "", 1, &zoom
,
1980 NULL
, 1, 10, 300, NULL
);
1992 rb
->set_bool("Resize Covers", &resize
);
1993 if (old_val
== resize
) /* changed? */
1995 /* fallthrough if changed, since cache needs to be rebuilt */
1998 rb
->remove(EMPTY_SLIDE
);
1999 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2002 case MENU_ATTACHED_USB
:
2003 return PLUGIN_USB_CONNECTED
;
2005 } while ( selection
>= 0 );
2006 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2019 rb
->lcd_set_foreground(N_BRIGHT(255));
2022 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2023 "Settings", "Return", "Quit");
2025 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2027 result
= settings_menu();
2028 if ( result
!= 0 ) return result
;
2037 case MENU_ATTACHED_USB
:
2038 return PLUGIN_USB_CONNECTED
;
2047 Animation step for zooming into the current cover
2049 void update_cover_in_animation(void)
2051 cover_animation_keyframe
++;
2052 if( cover_animation_keyframe
< 20 ) {
2053 center_slide
.distance
-=5;
2054 center_slide
.angle
+=1;
2057 else if( cover_animation_keyframe
< 35 ) {
2058 center_slide
.angle
+=16;
2061 cover_animation_keyframe
= 0;
2062 pf_state
= pf_show_tracks
;
2067 Animation step for zooming out the current cover
2069 void update_cover_out_animation(void)
2071 cover_animation_keyframe
++;
2072 if( cover_animation_keyframe
<= 15 ) {
2073 center_slide
.angle
-=16;
2075 else if( cover_animation_keyframe
< 35 ) {
2076 center_slide
.distance
+=5;
2077 center_slide
.angle
-=1;
2081 cover_animation_keyframe
= 0;
2087 Draw a blue gradient at y with height h
2089 static inline void draw_gradient(int y
, int h
)
2091 static int r
, inc
, c
;
2092 inc
= (100 << 8) / h
;
2094 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2095 int c2
= selected_track_pulse
- 5;
2096 for (r
=0; r
<h
; r
++) {
2097 #ifdef HAVE_LCD_COLOR
2098 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2101 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2103 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2112 static void track_list_yh(int char_height
)
2114 switch (show_album_name
)
2116 case album_name_hide
:
2117 track_list_y
= (show_fps
? char_height
: 0);
2118 track_list_h
= LCD_HEIGHT
- track_list_y
;
2120 case album_name_bottom
:
2121 track_list_y
= (show_fps
? char_height
: 0);
2122 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2124 default: /* case album_name_top */
2125 track_list_y
= char_height
* 2;
2126 track_list_h
= LCD_HEIGHT
- track_list_y
-
2127 (show_fps
? char_height
: 0);
2133 Reset the track list after a album change
2135 void reset_track_list(void)
2137 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2138 track_list_yh(albumtxt_h
);
2139 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2140 start_index_track_list
= 0;
2141 track_scroll_index
= 0;
2142 track_scroll_dir
= 1;
2145 /* let the tracklist start more centered
2146 * if the screen isn't filled with tracks */
2147 if (track_count
*albumtxt_h
< track_list_h
)
2149 track_list_h
= track_count
* albumtxt_h
;
2150 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2155 Display the list of tracks
2157 void show_track_list(void)
2159 MYLCD(clear_display
)();
2160 if ( center_slide
.slide_index
!= track_index
) {
2161 create_track_index(center_slide
.slide_index
);
2164 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2165 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2167 int titletxt_y
= track_list_y
;
2169 track_i
= start_index_track_list
;
2170 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2173 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2174 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2175 if ( track_i
== selected_track
) {
2176 draw_gradient(titletxt_y
, titletxt_h
);
2177 MYLCD(set_foreground
)(G_BRIGHT(255));
2178 if (titletxt_w
> LCD_WIDTH
) {
2179 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2180 track_scroll_dir
= 1;
2181 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2182 track_scroll_index
+= track_scroll_dir
*2;
2183 titletxt_x
= track_scroll_index
;
2185 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2188 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2189 MYLCD(set_foreground
)(G_BRIGHT(color
));
2190 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2192 titletxt_y
+= titletxt_h
;
2196 void select_next_track(void)
2198 if ( selected_track
< track_count
- 1 ) {
2200 track_scroll_index
= 0;
2201 track_scroll_dir
= 1;
2202 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2203 start_index_track_list
++;
2207 void select_prev_track(void)
2209 if (selected_track
> 0 ) {
2210 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2211 track_scroll_index
= 0;
2212 track_scroll_dir
= 1;
2218 Draw the current album name
2220 void draw_album_text(void)
2222 if (0 == show_album_name
)
2225 int albumtxt_w
, albumtxt_h
;
2230 /* Draw album text */
2231 if ( pf_state
== pf_scrolling
) {
2232 c
= ((slide_frame
& 0xffff )/ 255);
2233 if (step
< 0) c
= 255-c
;
2234 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2235 albumtxt
= get_album_name(center_index
+step
);
2239 albumtxt
= get_album_name(center_index
);
2245 albumtxt
= get_album_name(center_index
);
2248 MYLCD(set_foreground
)(G_BRIGHT(c
));
2249 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2250 if (center_index
!= prev_center_index
) {
2253 prev_center_index
= center_index
;
2256 if (show_album_name
== album_name_top
)
2257 albumtxt_y
= albumtxt_h
/ 2;
2259 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2261 if (albumtxt_w
> LCD_WIDTH
) {
2262 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2263 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2264 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2265 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2266 albumtxt_x
+= albumtxt_dir
;
2270 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2278 Main function that also contain the main plasma
2285 rb
->lcd_setfont(FONT_UI
);
2286 draw_splashscreen();
2288 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2289 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2290 rb
->splash(HZ
, "Could not create directory " CACHE_PREFIX
);
2291 return PLUGIN_ERROR
;
2295 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2297 init_reflect_table();
2299 ALIGN_BUFFER(buf
, buf_size
, 4);
2300 ret
= create_album_index();
2301 if (ret
== ERROR_BUFFER_FULL
) {
2302 rb
->splash(HZ
, "Not enough memory for album names");
2303 return PLUGIN_ERROR
;
2304 } else if (ret
== ERROR_NO_ALBUMS
) {
2305 rb
->splash(HZ
, "No albums found. Please enable database");
2306 return PLUGIN_ERROR
;
2309 ALIGN_BUFFER(buf
, buf_size
, 4);
2310 number_of_slides
= album_count
;
2311 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2312 rb
->splash(HZ
, "Could not create album art cache");
2313 return PLUGIN_ERROR
;
2316 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2317 rb
->splash(HZ
, "Could not load the empty slide");
2318 return PLUGIN_ERROR
;
2320 cache_version
= CACHE_VERSION
;
2321 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2326 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2327 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2329 rb
->splash(HZ
, "Greylib init failed!");
2330 return PLUGIN_ERROR
;
2332 grey_setfont(FONT_UI
);
2333 buf_size
-= grey_buf_used
;
2334 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2336 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2338 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2340 rb
->splash(HZ
, "Unable to load empty slide image");
2341 return PLUGIN_ERROR
;
2344 if (!create_pf_thread()) {
2345 rb
->splash(HZ
, "Cannot create thread!");
2346 return PLUGIN_ERROR
;
2352 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2355 cache
[i
].next
= i
+ 1;
2356 cache
[i
].prev
= i
- 1;
2358 cache
[0].prev
= i
- 1;
2359 cache
[i
- 1].next
= 0;
2379 long last_update
= *rb
->current_tick
;
2380 long current_update
;
2381 long update_interval
= 100;
2385 bool instant_update
;
2388 grey_set_drawmode(DRMODE_FG
);
2390 rb
->lcd_set_drawmode(DRMODE_FG
);
2392 current_update
= *rb
->current_tick
;
2395 /* Initial rendering */
2396 instant_update
= false;
2399 switch ( pf_state
) {
2401 update_scroll_animation();
2402 render_all_slides();
2403 instant_update
= true;
2406 update_cover_in_animation();
2407 render_all_slides();
2408 instant_update
= true;
2411 update_cover_out_animation();
2412 render_all_slides();
2413 instant_update
= true;
2415 case pf_show_tracks
:
2419 render_all_slides();
2424 if (current_update
- last_update
> update_interval
) {
2425 fps
= frames
* HZ
/ (current_update
- last_update
);
2426 last_update
= current_update
;
2433 MYLCD(set_foreground
)(G_BRIGHT(255));
2435 MYLCD(set_foreground
)(G_PIX(255,0,0));
2437 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2438 if (show_album_name
== album_name_top
)
2439 fpstxt_y
= LCD_HEIGHT
-
2440 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2443 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2448 /* Copy offscreen buffer to LCD and give time to other threads */
2452 /*/ Handle buttons */
2453 button
= rb
->get_custom_action(CONTEXT_CUSTOM
|
2454 (pf_state
== pf_show_tracks
? 1 : 0),
2455 instant_update
? 0 : HZ
/16,
2463 if ( pf_state
== pf_show_tracks
)
2464 pf_state
= pf_cover_out
;
2465 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2474 if ( ret
== -1 ) return PLUGIN_OK
;
2475 if ( ret
!= 0 ) return i
;
2479 MYLCD(set_drawmode
)(DRMODE_FG
);
2483 case PF_NEXT_REPEAT
:
2484 if ( pf_state
== pf_show_tracks
)
2485 select_next_track();
2486 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2491 case PF_PREV_REPEAT
:
2492 if ( pf_state
== pf_show_tracks
)
2493 select_prev_track();
2494 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2495 show_previous_slide();
2499 if ( pf_state
== pf_idle
) {
2500 pf_state
= pf_cover_in
;
2505 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2506 == SYS_USB_CONNECTED
)
2507 return PLUGIN_USB_CONNECTED
;
2515 /*************************** Plugin entry point ****************************/
2517 enum plugin_status
plugin_start(const void *parameter
)
2522 rb
->lcd_set_backdrop(NULL
);
2524 /* Turn off backlight timeout */
2525 backlight_force_on(); /* backlight control in lib/helper.c */
2526 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2527 rb
->cpu_boost(true);
2529 #if PLUGIN_BUFFER_SIZE > 0x10000
2530 buf
= rb
->plugin_get_buffer(&buf_size
);
2532 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2535 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2536 rb
->cpu_boost(false);
2538 if ( ret
== PLUGIN_OK
) {
2539 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2542 rb
->splash(HZ
, "Error writing config.");