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_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
)
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_MUL((*qp
++) + ctx
->round
, ctx
->divisor
);
642 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
643 uint32_t rb_mul
= SCALE_VAL(ctx
->divisor
, 31),
644 rb_rnd
= SCALE_VAL(ctx
->round
, 31),
645 g_mul
= SCALE_VAL(ctx
->divisor
, 63),
646 g_rnd
= SCALE_VAL(ctx
->round
, 63);
648 for (; dest
< end
; dest
+= ctx
->bm
->height
)
650 r
= SC_MUL(qp
->r
+ rb_rnd
, rb_mul
);
651 g
= SC_MUL(qp
->g
+ g_rnd
, g_mul
);
652 b
= SC_MUL(qp
->b
+ rb_rnd
, rb_mul
);
654 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
659 #ifdef HAVE_LCD_COLOR
660 static void output_row_32_transposed_fromyuv(uint32_t row
, void * row_in
,
661 struct scaler_context
*ctx
)
663 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
664 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
665 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
666 for (; dest
< end
; dest
+= ctx
->bm
->height
)
668 unsigned r
, g
, b
, y
, u
, v
;
669 y
= SC_MUL(qp
->b
+ ctx
->round
, ctx
->divisor
);
670 u
= SC_MUL(qp
->g
+ ctx
->round
, ctx
->divisor
);
671 v
= SC_MUL(qp
->r
+ ctx
->round
, ctx
->divisor
);
673 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
674 r
= (31 * r
+ (r
>> 3) + 127) >> 8;
675 g
= (63 * g
+ (g
>> 2) + 127) >> 8;
676 b
= (31 * b
+ (b
>> 3) + 127) >> 8;
677 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
682 static unsigned int get_size(struct bitmap
*bm
)
684 return bm
->width
* bm
->height
* sizeof(pix_t
);
687 const struct custom_format format_transposed
= {
688 .output_row_8
= output_row_8_transposed
,
689 #ifdef HAVE_LCD_COLOR
691 output_row_32_transposed
,
692 output_row_32_transposed_fromyuv
695 .output_row_32
= output_row_32_transposed
,
700 static const struct button_mapping
* get_context_map(int context
)
702 return pf_contexts
[context
& ~CONTEXT_CUSTOM
];
705 /* Create the lookup table with the scaling values for the reflections */
706 void init_reflect_table(void)
709 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
711 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
712 (5 * REFLECT_HEIGHT
);
716 Create an index of all albums from the database.
717 Also store the album names so we can access them later.
719 int create_album_index(void)
721 buf_size
-= UNIQBUF_SIZE
* sizeof(long);
722 long *uniqbuf
= (long *)(buf_size
+ (char *)buf
);
723 album
= ((struct album_data
*)uniqbuf
) - 1;
724 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
726 rb
->tagcache_search(&tcs
, tag_album
);
727 rb
->tagcache_search_set_uniqbuf(&tcs
, uniqbuf
, UNIQBUF_SIZE
);
728 unsigned int l
, old_l
= 0;
730 album
[0].name_idx
= 0;
731 while (rb
->tagcache_get_next(&tcs
))
733 buf_size
-= sizeof(struct album_data
);
735 if ( album_count
> 0 )
736 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
739 /* not enough memory */
740 return ERROR_BUFFER_FULL
;
742 rb
->strcpy(buf
, tcs
.result
);
744 buf
= l
+ (char *)buf
;
745 album
[-album_count
].seek
= tcs
.result_seek
;
749 rb
->tagcache_search_finish(&tcs
);
750 ALIGN_BUFFER(buf
, buf_size
, 4);
752 struct album_data
* tmp_album
= (struct album_data
*)buf
;
753 for (i
= album_count
- 1; i
>= 0; i
--)
754 tmp_album
[i
] = album
[-i
];
756 buf
= album
+ album_count
;
757 buf_size
+= UNIQBUF_SIZE
* sizeof(long);
758 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
762 Return a pointer to the album name of the given slide_index
764 char* get_album_name(const int slide_index
)
766 return album_names
+ album
[slide_index
].name_idx
;
770 Return a pointer to the track name of the active album
771 create_track_index has to be called first.
773 char* get_track_name(const int track_index
)
775 if ( track_index
< track_count
)
776 return track_names
+ tracks
[track_index
].name_idx
;
781 Compare two unsigned ints passed via pointers.
783 int compare_uints (const void *a_v
, const void *b_v
)
785 uint32_t a
= *(uint32_t *)a_v
;
786 uint32_t b
= *(uint32_t *)b_v
;
791 Create the track index of the given slide_index.
793 int create_track_index(const int slide_index
)
795 if ( slide_index
== track_index
) {
799 if (!rb
->tagcache_search(&tcs
, tag_title
))
802 struct track_data temp_tracks
[MAX_TRACKS
];
803 uint32_t temp_tracknums
[MAX_TRACKS
];
805 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
807 int string_index
= 0, i
, track_num
;
809 while (rb
->tagcache_get_next(&tcs
) && track_count
< MAX_TRACKS
)
811 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
);
812 int avail
= sizeof(track_names
) - string_index
;
816 len
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
817 "%d: %s", track_num
, tcs
.result
);
822 len
= tcs
.result_len
;
823 rb
->strncpy(track_names
+ string_index
, tcs
.result
, avail
);
827 temp_tracknums
[track_count
] = (track_num
<< 8) + track_count
;
828 temp_tracks
[track_count
].name_idx
= string_index
;
829 temp_tracks
[track_count
].seek
= tcs
.result_seek
;
834 rb
->tagcache_search_finish(&tcs
);
835 track_index
= slide_index
;
837 /* now fix the track list order */
838 rb
->qsort(temp_tracknums
, track_count
, sizeof(int), compare_uints
);
839 for (i
= 0; i
< track_count
; i
++)
841 tracks
[i
].name_idx
= temp_tracks
[0xFF & temp_tracknums
[i
]].name_idx
;
842 tracks
[i
].seek
= temp_tracks
[0xFF & temp_tracknums
[i
]].seek
;
844 return (track_count
> 0) ? 0 : -1;
848 Determine filename of the album art for the given slide_index and
849 store the result in buf.
850 The algorithm looks for the first track of the given album uses
851 find_albumart to find the filename.
853 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
856 if ( slide_index
== -1 )
858 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
861 if (!rb
->tagcache_search(&tcs
, tag_filename
))
865 /* find the first track of the album */
866 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
868 if ( rb
->tagcache_get_next(&tcs
) ) {
872 fd
= rb
->open(tcs
.result
, O_RDONLY
);
873 rb
->get_metadata(&id3
, fd
, tcs
.result
);
875 if ( search_albumart_files(&id3
, "", buf
, buflen
) )
881 /* did not find a matching track */
884 rb
->tagcache_search_finish(&tcs
);
889 Draw the PictureFlow logo
891 void draw_splashscreen(void)
893 struct screen
* display
= rb
->screens
[0];
894 const struct picture
* logo
= &(logos
[display
->screen_type
]);
897 rb
->lcd_set_background(N_BRIGHT(0));
898 rb
->lcd_set_foreground(N_BRIGHT(255));
900 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
902 rb
->lcd_clear_display();
904 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
905 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
906 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
907 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
909 picture_draw(display
, logo
, (LCD_WIDTH
- logo
->width
) / 2, 10);
917 Draw a simple progress bar
919 void draw_progressbar(int step
)
922 const int bar_height
= 22;
923 const int w
= LCD_WIDTH
- 20;
926 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
928 int y
= (LCD_HEIGHT
- txt_h
)/2;
930 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
934 rb
->lcd_set_foreground(N_BRIGHT(100));
936 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
938 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
941 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
943 rb
->lcd_set_foreground(N_BRIGHT(255));
950 Precomupte the album art images and store them in CACHE_PREFIX.
952 bool create_albumart_cache(void)
957 struct bitmap input_bmp
;
959 char pfraw_file
[MAX_PATH
];
960 char albumart_file
[MAX_PATH
];
961 unsigned int format
= FORMAT_NATIVE
;
963 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
965 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
966 for (i
=0; i
< album_count
; i
++)
968 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
970 /* delete existing cache, so it's a true rebuild */
971 if(rb
->file_exists(pfraw_file
))
972 rb
->remove(pfraw_file
);
974 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
977 input_bmp
.data
= buf
;
978 input_bmp
.width
= DISPLAY_WIDTH
;
979 input_bmp
.height
= DISPLAY_HEIGHT
;
980 ret
= read_image_file(albumart_file
, &input_bmp
,
981 buf_size
, format
, &format_transposed
);
983 rb
->splash(HZ
, "Could not read bmp");
984 continue; /* skip missing/broken files */
986 if (!save_pfraw(pfraw_file
, &input_bmp
))
988 rb
->splash(HZ
, "Could not write bmp");
991 if ( rb
->button_get(false) == PF_MENU
) return false;
994 /* Warn the user that we couldn't find any albumart */
995 rb
->splash(2*HZ
, "No album art found");
1002 Thread used for loading and preparing bitmaps in the background
1006 long sleep_time
= 5 * HZ
;
1007 struct queue_event ev
;
1009 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1014 /* we just woke up */
1017 while ( load_new_slide() ) {
1029 End the thread by posting the EV_EXIT event
1031 void end_pf_thread(void)
1033 if ( thread_is_running
) {
1034 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
1035 rb
->thread_wait(thread_id
);
1036 /* remove the thread's queue from the broadcast list */
1037 rb
->queue_delete(&thread_q
);
1038 thread_is_running
= false;
1045 Create the thread an setup the event queue
1047 bool create_pf_thread(void)
1049 /* put the thread's queue in the bcast list */
1050 rb
->queue_init(&thread_q
, true);
1051 if ((thread_id
= rb
->create_thread(
1054 sizeof(thread_stack
),
1056 "Picture load thread"
1057 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1058 PRIORITY_REALTIME
+ 1))
1064 thread_is_running
= true;
1065 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1070 Safe the given bitmap as filename in the pfraw format
1072 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1074 struct pfraw_header bmph
;
1075 bmph
.width
= bm
->width
;
1076 bmph
.height
= bm
->height
;
1077 int fh
= rb
->creat( filename
);
1078 if( fh
< 0 ) return false;
1079 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1081 for( y
= 0; y
< bm
->height
; y
++ )
1083 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
1084 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
1092 * The following functions implement the linked-list-in-array used to manage
1093 * the LRU cache of slides, and the list of free cache slots.
1096 #define seek_right_while(start, cond) \
1098 int ind_, next_ = (start); \
1101 next_ = cache[ind_].next; \
1102 } while (next_ != cache_used && (cond)); \
1106 #define seek_left_while(start, cond) \
1108 int ind_, next_ = (start); \
1111 next_ = cache[ind_].prev; \
1112 } while (ind_ != cache_used && (cond)); \
1117 Pop the given item from the linked list starting at *head, returning the next
1118 item, or -1 if the list is now empty.
1120 static inline int lla_pop_item (int *head
, int i
)
1122 int prev
= cache
[i
].prev
;
1123 int next
= cache
[i
].next
;
1129 else if (i
== *head
)
1131 cache
[next
].prev
= prev
;
1132 cache
[prev
].next
= next
;
1138 Pop the head item from the list starting at *head, returning the index of the
1139 item, or -1 if the list is already empty.
1141 static inline int lla_pop_head (int *head
)
1145 lla_pop_item(head
, i
);
1150 Insert the item at index i before the one at index p.
1152 static inline void lla_insert (int i
, int p
)
1155 int prev
= cache
[next
].prev
;
1156 cache
[next
].prev
= i
;
1157 cache
[prev
].next
= i
;
1158 cache
[i
].next
= next
;
1159 cache
[i
].prev
= prev
;
1164 Insert the item at index i at the end of the list starting at *head.
1166 static inline void lla_insert_tail (int *head
, int i
)
1174 lla_insert(i
, *head
);
1178 Insert the item at index i before the one at index p.
1180 static inline void lla_insert_after(int i
, int p
)
1188 Insert the item at index i before the one at index p in the list starting at
1191 static inline void lla_insert_before(int *head
, int i
, int p
)
1200 Free the used slide at index i, and its buffer, and move it to the free
1203 static inline void free_slide(int i
)
1205 if (cache
[i
].hid
!= empty_slide_hid
)
1206 buflib_free(&buf_ctx
, cache
[i
].hid
);
1207 cache
[i
].index
= -1;
1208 lla_pop_item(&cache_used
, i
);
1209 lla_insert_tail(&cache_free
, i
);
1210 if (cache_used
== -1)
1212 cache_right_index
= -1;
1213 cache_left_index
= -1;
1214 cache_center_index
= -1;
1220 Free one slide ranked above the given priority. If no such slide can be found,
1223 static inline int free_slide_prio(int prio
)
1225 if (cache_used
== -1)
1227 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1228 int prio_l
= cache
[l
].index
< center_index
?
1229 center_index
- cache
[l
].index
: 0;
1230 int prio_r
= cache
[r
].index
> center_index
?
1231 cache
[r
].index
- center_index
: 0;
1232 if (prio_l
> prio_r
)
1240 if (prio_max
> prio
)
1242 if (i
== cache_left_index
)
1243 cache_left_index
= cache
[i
].next
;
1244 if (i
== cache_right_index
)
1245 cache_right_index
= cache
[i
].prev
;
1253 Read the pfraw image given as filename and return the hid of the buffer
1255 int read_pfraw(char* filename
, int prio
)
1257 struct pfraw_header bmph
;
1258 int fh
= rb
->open(filename
, O_RDONLY
);
1260 return empty_slide_hid
;
1262 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1264 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1265 bmph
.width
* bmph
.height
;
1268 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1275 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1277 bm
->width
= bmph
.width
;
1278 bm
->height
= bmph
.height
;
1279 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1282 for( y
= 0; y
< bm
->height
; y
++ )
1284 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1293 Load the surface for the given slide_index into the cache at cache_index.
1295 static inline bool load_and_prepare_surface(const int slide_index
,
1296 const int cache_index
,
1299 char tmp_path_name
[MAX_PATH
+1];
1300 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1303 int hid
= read_pfraw(tmp_path_name
, prio
);
1307 cache
[cache_index
].hid
= hid
;
1309 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1310 cache
[cache_index
].index
= slide_index
;
1318 Load the "next" slide that we can load, freeing old slides if needed, provided
1319 that they are further from center_index than the current slide
1321 bool load_new_slide(void)
1324 if (cache_center_index
!= -1)
1327 if (cache
[cache_center_index
].index
!= center_index
)
1329 if (cache
[cache_center_index
].index
< center_index
)
1331 cache_center_index
= seek_right_while(cache_center_index
,
1332 cache
[next_
].index
<= center_index
);
1333 prev
= cache_center_index
;
1334 next
= cache
[cache_center_index
].next
;
1338 cache_center_index
= seek_left_while(cache_center_index
,
1339 cache
[next_
].index
>= center_index
);
1340 next
= cache_center_index
;
1341 prev
= cache
[cache_center_index
].prev
;
1343 if (cache
[cache_center_index
].index
!= center_index
)
1345 if (cache_free
== -1)
1347 i
= lla_pop_head(&cache_free
);
1348 if (!load_and_prepare_surface(center_index
, i
, 0))
1349 goto fail_and_refree
;
1350 if (cache
[next
].index
== -1)
1352 if (cache
[prev
].index
== -1)
1353 goto insert_first_slide
;
1355 next
= cache
[prev
].next
;
1357 lla_insert(i
, next
);
1358 if (cache
[i
].index
< cache
[cache_used
].index
)
1360 cache_center_index
= i
;
1361 cache_left_index
= i
;
1362 cache_right_index
= i
;
1366 if (cache
[cache_left_index
].index
>
1367 cache
[cache_center_index
].index
)
1368 cache_left_index
= cache_center_index
;
1369 if (cache
[cache_right_index
].index
<
1370 cache
[cache_center_index
].index
)
1371 cache_right_index
= cache_center_index
;
1372 cache_left_index
= seek_left_while(cache_left_index
,
1373 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1374 cache_right_index
= seek_right_while(cache_right_index
,
1375 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1376 int prio_l
= cache
[cache_center_index
].index
-
1377 cache
[cache_left_index
].index
+ 1;
1378 int prio_r
= cache
[cache_right_index
].index
-
1379 cache
[cache_center_index
].index
+ 1;
1380 if ((prio_l
< prio_r
||
1381 cache
[cache_right_index
].index
>= number_of_slides
) &&
1382 cache
[cache_left_index
].index
> 0)
1384 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1386 i
= lla_pop_head(&cache_free
);
1387 if (load_and_prepare_surface(cache
[cache_left_index
].index
1390 lla_insert_before(&cache_used
, i
, cache_left_index
);
1391 cache_left_index
= i
;
1394 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1396 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1398 i
= lla_pop_head(&cache_free
);
1399 if (load_and_prepare_surface(cache
[cache_right_index
].index
1402 lla_insert_after(i
, cache_right_index
);
1403 cache_right_index
= i
;
1408 i
= lla_pop_head(&cache_free
);
1409 if (load_and_prepare_surface(center_index
, i
, 0))
1414 cache_center_index
= i
;
1415 cache_left_index
= i
;
1416 cache_right_index
= i
;
1424 lla_insert_tail(&cache_free
, i
);
1431 Get a slide from the buffer
1433 static inline struct dim
*get_slide(const int hid
)
1440 bmp
= buflib_get_data(&buf_ctx
, hid
);
1447 Return the requested surface
1449 static inline struct dim
*surface(const int slide_index
)
1451 if (slide_index
< 0)
1453 if (slide_index
>= number_of_slides
)
1456 if ((i
= cache_used
) != -1)
1459 if (cache
[i
].index
== slide_index
)
1460 return get_slide(cache
[i
].hid
);
1462 } while (i
!= cache_used
);
1464 return get_slide(empty_slide_hid
);
1468 adjust slides so that they are in "steady state" position
1470 void reset_slides(void)
1472 center_slide
.angle
= 0;
1473 center_slide
.cx
= 0;
1474 center_slide
.cy
= 0;
1475 center_slide
.distance
= 0;
1476 center_slide
.slide_index
= center_index
;
1479 for (i
= 0; i
< num_slides
; i
++) {
1480 struct slide_data
*si
= &left_slides
[i
];
1482 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1484 si
->slide_index
= center_index
- 1 - i
;
1488 for (i
= 0; i
< num_slides
; i
++) {
1489 struct slide_data
*si
= &right_slides
[i
];
1491 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1493 si
->slide_index
= center_index
+ 1 + i
;
1500 Updates look-up table and other stuff necessary for the rendering.
1501 Call this when the viewport size or slide dimension is changed.
1503 * To calculate the offset that will provide the proper margin, we use the same
1504 * projection used to render the slides. The solution for xc, the slide center,
1506 * xp * (zo + xs * sin(r))
1507 * xc = xp - xs * cos(r) + ───────────────────────
1509 * TODO: support moving the side slides toward or away from the camera
1511 void recalc_offsets(void)
1513 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1515 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1516 PFREAL_ONE
) * zoom
/ 100;
1519 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1520 cosr
= fcos(-itilt
);
1521 sinr
= fsin(-itilt
);
1522 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1523 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1524 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1525 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1527 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1532 Fade the given color by spreading the fb_data (ushort)
1533 to an uint, multiply and compress the result back to a ushort.
1535 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1536 static inline unsigned fade_color(pix_t c
, unsigned a
)
1538 unsigned int result
;
1540 a
= (a
+ 2) & 0x1fc;
1541 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1542 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1544 return swap16(result
);
1546 #elif LCD_PIXELFORMAT == RGB565
1547 static inline unsigned fade_color(pix_t c
, unsigned a
)
1549 unsigned int result
;
1550 a
= (a
+ 2) & 0x1fc;
1551 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1552 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1557 static inline unsigned fade_color(pix_t c
, unsigned a
)
1560 return MULUQ(val
, a
) >> 8;
1565 * Render a single slide
1566 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1567 * on the slide from its center, zo is the slide's depth offset from the plane
1568 * of the display, r is the angle at which the slide is tilted, and xp is the
1569 * point on the display corresponding to xs on the slide, the projection
1572 * z * (xc + xs * cos(r))
1573 * xp = ──────────────────────
1574 * z + zo + xs * sin(r)
1576 * z * (xc - xp) - xp * zo
1577 * xs = ────────────────────────
1578 * xp * sin(r) - z * cos(r)
1580 * We use the xp projection once, to find the left edge of the slide on the
1581 * display. From there, we use the xs reverse projection to find the horizontal
1582 * offset from the slide center of each column on the screen, until we reach
1583 * the right edge of the slide, or the screen. The reverse projection can be
1584 * optimized by saving the numerator and denominator of the fraction, which can
1585 * then be incremented by (z + zo) and sin(r) respectively.
1587 void render_slide(struct slide_data
*slide
, const int alpha
)
1589 struct dim
*bmp
= surface(slide
->slide_index
);
1593 if (slide
->angle
> 255 || slide
->angle
< -255)
1595 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1597 const int sw
= bmp
->width
;
1598 const int sh
= bmp
->height
;
1599 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1600 const int w
= LCD_WIDTH
;
1602 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1604 if (alpha
== 256) { /* opaque -> copy table */
1605 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1606 } else { /* precalculate faded table */
1608 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1609 lalpha
= reflect_table
[i
];
1610 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1614 PFreal cosr
= fcos(slide
->angle
);
1615 PFreal sinr
= fsin(slide
->angle
);
1616 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1617 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1618 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1619 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1620 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1622 /* Since we're finding the screen position of the left edge of the slide,
1625 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1627 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1631 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1632 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1633 xs
= fdiv(xsnum
, xsden
);
1635 xsnumi
= -CAM_DIST_R
- zo
;
1638 int dy
= PFREAL_ONE
;
1639 for (x
= xi
; x
< w
; x
++) {
1640 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1643 if (zo
|| slide
->angle
)
1644 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1646 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1647 const int pixelstep
= BUFFER_WIDTH
;
1649 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1650 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1651 pix_t
*pixel
= &buffer
[((LCD_HEIGHT
/2)-1)*BUFFER_WIDTH
+ x
];
1655 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1661 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1666 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1667 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1668 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1669 p
+ (LCD_HEIGHT
/2) * dy
);
1670 pixel
= &buffer
[(LCD_HEIGHT
/2)*BUFFER_WIDTH
+ x
];
1674 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1680 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1686 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1687 int lalpha
= reftab
[ty
];
1688 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1693 if (zo
|| slide
->angle
)
1697 xs
= fdiv(xsnum
, xsden
);
1702 /* let the music play... */
1709 Jump the the given slide_index
1711 static inline void set_current_slide(const int slide_index
)
1713 int old_center_index
= center_index
;
1715 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1716 if (old_center_index
!= center_index
)
1717 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1718 target
= center_index
;
1719 slide_frame
= slide_index
<< 16;
1724 Start the animation for changing slides
1726 void start_animation(void)
1728 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1729 pf_state
= pf_scrolling
;
1733 Go to the previous slide
1735 void show_previous_slide(void)
1738 if (center_index
> 0) {
1739 target
= center_index
- 1;
1742 } else if ( step
> 0 ) {
1743 target
= center_index
;
1746 target
= fmax(0, center_index
- 2);
1752 Go to the next slide
1754 void show_next_slide(void)
1757 if (center_index
< number_of_slides
- 1) {
1758 target
= center_index
+ 1;
1761 } else if ( step
< 0 ) {
1762 target
= center_index
;
1765 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1771 Render the slides. Updates only the offscreen buffer.
1773 void render_all_slides(void)
1775 MYLCD(set_background
)(G_BRIGHT(0));
1776 /* TODO: Optimizes this by e.g. invalidating rects */
1777 MYLCD(clear_display
)();
1779 int nleft
= num_slides
;
1780 int nright
= num_slides
;
1784 /* no animation, boring plain rendering */
1785 for (index
= nleft
- 2; index
>= 0; index
--) {
1786 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1787 alpha
-= extra_fade
;
1789 render_slide(&left_slides
[index
], alpha
);
1791 for (index
= nright
- 2; index
>= 0; index
--) {
1792 int alpha
= (index
< nright
- 2) ? 256 : 128;
1793 alpha
-= extra_fade
;
1795 render_slide(&right_slides
[index
], alpha
);
1798 /* the first and last slide must fade in/fade out */
1799 for (index
= nleft
- 1; index
>= 0; index
--) {
1801 if (index
== nleft
- 1)
1802 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1803 if (index
== nleft
- 2)
1804 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1805 if (index
== nleft
- 3)
1806 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1807 render_slide(&left_slides
[index
], alpha
);
1809 for (index
= nright
- 1; index
>= 0; index
--) {
1810 int alpha
= (index
< nright
- 2) ? 256 : 128;
1811 if (index
== nright
- 1)
1812 alpha
= (step
> 0) ? fade
/ 2 : 0;
1813 if (index
== nright
- 2)
1814 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1815 if (index
== nright
- 3)
1816 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1817 render_slide(&right_slides
[index
], alpha
);
1820 render_slide(¢er_slide
, 256);
1825 Updates the animation effect. Call this periodically from a timer.
1827 void update_scroll_animation(void)
1835 /* deaccelerate when approaching the target */
1837 const int max
= 2 * 65536;
1839 int fi
= slide_frame
;
1840 fi
-= (target
<< 16);
1845 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1846 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1849 slide_frame
+= speed
* step
;
1851 int index
= slide_frame
>> 16;
1852 int pos
= slide_frame
& 0xffff;
1853 int neg
= 65536 - pos
;
1854 int tick
= (step
< 0) ? neg
: pos
;
1855 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1857 /* the leftmost and rightmost slide must fade away */
1862 if (center_index
!= index
) {
1863 center_index
= index
;
1864 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1865 slide_frame
= index
<< 16;
1866 center_slide
.slide_index
= center_index
;
1867 for (i
= 0; i
< num_slides
; i
++)
1868 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1869 for (i
= 0; i
< num_slides
; i
++)
1870 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1873 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1874 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1875 center_slide
.cy
= fmul(offsetY
, ftick
);
1877 if (center_index
== target
) {
1885 for (i
= 0; i
< num_slides
; i
++) {
1886 struct slide_data
*si
= &left_slides
[i
];
1889 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1890 * slide_spacing
* ftick
);
1894 for (i
= 0; i
< num_slides
; i
++) {
1895 struct slide_data
*si
= &right_slides
[i
];
1898 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1899 * slide_spacing
* ftick
;
1904 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1905 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1906 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1907 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1909 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1910 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1911 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1912 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1915 /* must change direction ? */
1928 void cleanup(void *parameter
)
1931 /* Turn on backlight timeout (revert to settings) */
1932 backlight_use_settings(); /* backlight control in lib/helper.c */
1940 Create the "?" slide, that is shown while loading
1941 or when no cover was found.
1943 int create_empty_slide(bool force
)
1945 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1946 struct bitmap input_bmp
;
1948 input_bmp
.width
= DISPLAY_WIDTH
;
1949 input_bmp
.height
= DISPLAY_HEIGHT
;
1951 input_bmp
.format
= FORMAT_NATIVE
;
1953 input_bmp
.data
= (char*)buf
;
1954 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1956 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1957 &format_transposed
);
1958 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1966 Shows the album name setting menu
1968 int album_name_menu(void)
1970 int selection
= show_album_name
;
1972 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
1973 "Hide album title", "Show at the bottom", "Show at the top");
1974 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
1976 show_album_name
= selection
;
1977 return GO_TO_PREVIOUS
;
1981 Shows the settings menu
1983 int settings_menu(void)
1988 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
1989 "Spacing", "Centre margin", "Number of slides", "Zoom",
1990 "Show album title", "Resize Covers", "Rebuild cache");
1993 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
1996 rb
->set_bool("Show FPS", &show_fps
);
2001 rb
->set_int("Spacing between slides", "", 1,
2003 NULL
, 1, 0, 100, NULL
);
2009 rb
->set_int("Centre margin", "", 1,
2011 NULL
, 1, 0, 80, NULL
);
2017 rb
->set_int("Number of slides", "", 1, &num_slides
,
2018 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2024 rb
->set_int("Zoom", "", 1, &zoom
,
2025 NULL
, 1, 10, 300, NULL
);
2037 rb
->set_bool("Resize Covers", &resize
);
2038 if (old_val
== resize
) /* changed? */
2040 /* fallthrough if changed, since cache needs to be rebuilt */
2043 rb
->remove(EMPTY_SLIDE
);
2044 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2047 case MENU_ATTACHED_USB
:
2048 return PLUGIN_USB_CONNECTED
;
2050 } while ( selection
>= 0 );
2051 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2064 rb
->lcd_set_foreground(N_BRIGHT(255));
2067 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2068 "Settings", "Return", "Quit");
2070 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2072 result
= settings_menu();
2073 if ( result
!= 0 ) return result
;
2082 case MENU_ATTACHED_USB
:
2083 return PLUGIN_USB_CONNECTED
;
2092 Animation step for zooming into the current cover
2094 void update_cover_in_animation(void)
2096 cover_animation_keyframe
++;
2097 if( cover_animation_keyframe
< 20 ) {
2098 center_slide
.distance
-=5;
2099 center_slide
.angle
+=1;
2102 else if( cover_animation_keyframe
< 35 ) {
2103 center_slide
.angle
+=16;
2106 cover_animation_keyframe
= 0;
2107 pf_state
= pf_show_tracks
;
2112 Animation step for zooming out the current cover
2114 void update_cover_out_animation(void)
2116 cover_animation_keyframe
++;
2117 if( cover_animation_keyframe
<= 15 ) {
2118 center_slide
.angle
-=16;
2120 else if( cover_animation_keyframe
< 35 ) {
2121 center_slide
.distance
+=5;
2122 center_slide
.angle
-=1;
2126 cover_animation_keyframe
= 0;
2132 Draw a blue gradient at y with height h
2134 static inline void draw_gradient(int y
, int h
)
2136 static int r
, inc
, c
;
2137 inc
= (100 << 8) / h
;
2139 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2140 int c2
= selected_track_pulse
- 5;
2141 for (r
=0; r
<h
; r
++) {
2142 #ifdef HAVE_LCD_COLOR
2143 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2146 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2148 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2157 static void track_list_yh(int char_height
)
2159 switch (show_album_name
)
2161 case album_name_hide
:
2162 track_list_y
= (show_fps
? char_height
: 0);
2163 track_list_h
= LCD_HEIGHT
- track_list_y
;
2165 case album_name_bottom
:
2166 track_list_y
= (show_fps
? char_height
: 0);
2167 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2169 default: /* case album_name_top */
2170 track_list_y
= char_height
* 2;
2171 track_list_h
= LCD_HEIGHT
- track_list_y
-
2172 (show_fps
? char_height
: 0);
2178 Reset the track list after a album change
2180 void reset_track_list(void)
2182 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2183 track_list_yh(albumtxt_h
);
2184 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2185 start_index_track_list
= 0;
2186 track_scroll_index
= 0;
2187 track_scroll_dir
= 1;
2190 /* let the tracklist start more centered
2191 * if the screen isn't filled with tracks */
2192 if (track_count
*albumtxt_h
< track_list_h
)
2194 track_list_h
= track_count
* albumtxt_h
;
2195 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2200 Display the list of tracks
2202 void show_track_list(void)
2204 MYLCD(clear_display
)();
2205 if ( center_slide
.slide_index
!= track_index
) {
2206 create_track_index(center_slide
.slide_index
);
2209 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2210 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2212 int titletxt_y
= track_list_y
;
2214 track_i
= start_index_track_list
;
2215 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2218 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2219 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2220 if ( track_i
== selected_track
) {
2221 draw_gradient(titletxt_y
, titletxt_h
);
2222 MYLCD(set_foreground
)(G_BRIGHT(255));
2223 if (titletxt_w
> LCD_WIDTH
) {
2224 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2225 track_scroll_dir
= 1;
2226 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2227 track_scroll_index
+= track_scroll_dir
*2;
2228 titletxt_x
= track_scroll_index
;
2230 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2233 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2234 MYLCD(set_foreground
)(G_BRIGHT(color
));
2235 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2237 titletxt_y
+= titletxt_h
;
2241 void select_next_track(void)
2243 if ( selected_track
< track_count
- 1 ) {
2245 track_scroll_index
= 0;
2246 track_scroll_dir
= 1;
2247 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2248 start_index_track_list
++;
2252 void select_prev_track(void)
2254 if (selected_track
> 0 ) {
2255 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2256 track_scroll_index
= 0;
2257 track_scroll_dir
= 1;
2263 Draw the current album name
2265 void draw_album_text(void)
2267 if (0 == show_album_name
)
2270 int albumtxt_w
, albumtxt_h
;
2275 /* Draw album text */
2276 if ( pf_state
== pf_scrolling
) {
2277 c
= ((slide_frame
& 0xffff )/ 255);
2278 if (step
< 0) c
= 255-c
;
2279 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2280 albumtxt
= get_album_name(center_index
+step
);
2284 albumtxt
= get_album_name(center_index
);
2290 albumtxt
= get_album_name(center_index
);
2293 MYLCD(set_foreground
)(G_BRIGHT(c
));
2294 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2295 if (center_index
!= prev_center_index
) {
2298 prev_center_index
= center_index
;
2301 if (show_album_name
== album_name_top
)
2302 albumtxt_y
= albumtxt_h
/ 2;
2304 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2306 if (albumtxt_w
> LCD_WIDTH
) {
2307 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2308 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2309 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2310 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2311 albumtxt_x
+= albumtxt_dir
;
2315 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2323 Main function that also contain the main plasma
2330 rb
->lcd_setfont(FONT_UI
);
2331 draw_splashscreen();
2333 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2334 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2335 rb
->splash(HZ
, "Could not create directory " CACHE_PREFIX
);
2336 return PLUGIN_ERROR
;
2340 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2342 init_reflect_table();
2344 ALIGN_BUFFER(buf
, buf_size
, 4);
2345 ret
= create_album_index();
2346 if (ret
== ERROR_BUFFER_FULL
) {
2347 rb
->splash(HZ
, "Not enough memory for album names");
2348 return PLUGIN_ERROR
;
2349 } else if (ret
== ERROR_NO_ALBUMS
) {
2350 rb
->splash(HZ
, "No albums found. Please enable database");
2351 return PLUGIN_ERROR
;
2354 ALIGN_BUFFER(buf
, buf_size
, 4);
2355 number_of_slides
= album_count
;
2356 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2357 rb
->splash(HZ
, "Could not create album art cache");
2358 return PLUGIN_ERROR
;
2361 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2362 rb
->splash(HZ
, "Could not load the empty slide");
2363 return PLUGIN_ERROR
;
2365 cache_version
= CACHE_VERSION
;
2366 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2371 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2372 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2374 rb
->splash(HZ
, "Greylib init failed!");
2375 return PLUGIN_ERROR
;
2377 grey_setfont(FONT_UI
);
2378 buf_size
-= grey_buf_used
;
2379 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2381 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2383 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2385 rb
->splash(HZ
, "Unable to load empty slide image");
2386 return PLUGIN_ERROR
;
2389 if (!create_pf_thread()) {
2390 rb
->splash(HZ
, "Cannot create thread!");
2391 return PLUGIN_ERROR
;
2397 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2400 cache
[i
].next
= i
+ 1;
2401 cache
[i
].prev
= i
- 1;
2403 cache
[0].prev
= i
- 1;
2404 cache
[i
- 1].next
= 0;
2424 long last_update
= *rb
->current_tick
;
2425 long current_update
;
2426 long update_interval
= 100;
2430 bool instant_update
;
2433 grey_set_drawmode(DRMODE_FG
);
2435 rb
->lcd_set_drawmode(DRMODE_FG
);
2437 current_update
= *rb
->current_tick
;
2440 /* Initial rendering */
2441 instant_update
= false;
2444 switch ( pf_state
) {
2446 update_scroll_animation();
2447 render_all_slides();
2448 instant_update
= true;
2451 update_cover_in_animation();
2452 render_all_slides();
2453 instant_update
= true;
2456 update_cover_out_animation();
2457 render_all_slides();
2458 instant_update
= true;
2460 case pf_show_tracks
:
2464 render_all_slides();
2469 if (current_update
- last_update
> update_interval
) {
2470 fps
= frames
* HZ
/ (current_update
- last_update
);
2471 last_update
= current_update
;
2478 MYLCD(set_foreground
)(G_BRIGHT(255));
2480 MYLCD(set_foreground
)(G_PIX(255,0,0));
2482 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2483 if (show_album_name
== album_name_top
)
2484 fpstxt_y
= LCD_HEIGHT
-
2485 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2488 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2493 /* Copy offscreen buffer to LCD and give time to other threads */
2497 /*/ Handle buttons */
2498 button
= rb
->get_custom_action(CONTEXT_CUSTOM
|
2499 (pf_state
== pf_show_tracks
? 1 : 0),
2500 instant_update
? 0 : HZ
/16,
2508 if ( pf_state
== pf_show_tracks
)
2509 pf_state
= pf_cover_out
;
2510 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2519 if ( ret
== -1 ) return PLUGIN_OK
;
2520 if ( ret
!= 0 ) return i
;
2524 MYLCD(set_drawmode
)(DRMODE_FG
);
2528 case PF_NEXT_REPEAT
:
2529 if ( pf_state
== pf_show_tracks
)
2530 select_next_track();
2531 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2536 case PF_PREV_REPEAT
:
2537 if ( pf_state
== pf_show_tracks
)
2538 select_prev_track();
2539 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2540 show_previous_slide();
2544 if ( pf_state
== pf_idle
) {
2545 pf_state
= pf_cover_in
;
2550 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2551 == SYS_USB_CONNECTED
)
2552 return PLUGIN_USB_CONNECTED
;
2560 /*************************** Plugin entry point ****************************/
2562 enum plugin_status
plugin_start(const void *parameter
)
2567 rb
->lcd_set_backdrop(NULL
);
2569 /* Turn off backlight timeout */
2570 backlight_force_on(); /* backlight control in lib/helper.c */
2571 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2572 rb
->cpu_boost(true);
2574 #if PLUGIN_BUFFER_SIZE > 0x10000
2575 buf
= rb
->plugin_get_buffer(&buf_size
);
2577 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2580 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2581 rb
->cpu_boost(false);
2583 if ( ret
== PLUGIN_OK
) {
2584 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2587 rb
->splash(HZ
, "Error writing config.");