1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
28 #include "lib/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/helper.h"
31 #include "lib/configfile.h"
33 #include "lib/feature_wrappers.h"
34 #include "lib/buflib.h"
38 /******************************* Globals ***********************************/
40 #define PF_PREV ACTION_STD_PREV
41 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
42 #define PF_NEXT ACTION_STD_NEXT
43 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
44 #define PF_SELECT ACTION_STD_OK
45 #define PF_CONTEXT ACTION_STD_CONTEXT
46 #define PF_BACK ACTION_STD_CANCEL
47 #define PF_MENU ACTION_STD_MENU
48 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
50 const struct button_mapping pf_context_album_scroll
[] =
52 #ifdef HAVE_TOUCHSCREEN
53 {PF_PREV
, BUTTON_MIDLEFT
, BUTTON_NONE
},
54 {PF_PREV_REPEAT
, BUTTON_MIDLEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
55 {PF_NEXT
, BUTTON_MIDRIGHT
, BUTTON_NONE
},
56 {PF_NEXT_REPEAT
, BUTTON_MIDRIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
58 #if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
59 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
60 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
61 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
62 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
63 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
64 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
65 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
66 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
67 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
68 CONFIG_KEYPAD == MEIZU_M6SL_PAD
69 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
70 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
71 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
72 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
73 #elif CONFIG_KEYPAD == ONDIO_PAD
74 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
75 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
76 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
77 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
78 {PF_SELECT
, BUTTON_UP
|BUTTON_REL
, BUTTON_UP
},
79 {PF_CONTEXT
, BUTTON_UP
|BUTTON_REPEAT
, BUTTON_UP
},
80 {ACTION_NONE
, BUTTON_UP
, BUTTON_NONE
},
81 {ACTION_NONE
, BUTTON_DOWN
, BUTTON_NONE
},
82 {ACTION_NONE
, BUTTON_DOWN
|BUTTON_REPEAT
, BUTTON_NONE
},
83 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REL
, BUTTON_RIGHT
},
84 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
85 {PF_PREV
, BUTTON_RC_REW
, BUTTON_NONE
},
86 {PF_PREV_REPEAT
, BUTTON_RC_REW
|BUTTON_REPEAT
,BUTTON_NONE
},
87 {PF_NEXT
, BUTTON_RC_FF
, BUTTON_NONE
},
88 {PF_NEXT_REPEAT
, BUTTON_RC_FF
|BUTTON_REPEAT
, BUTTON_NONE
},
90 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM
|1)
93 const struct button_mapping pf_context_buttons
[] =
95 #ifdef HAVE_TOUCHSCREEN
96 {PF_SELECT
, BUTTON_CENTER
, BUTTON_NONE
},
97 {PF_MENU
, BUTTON_TOPLEFT
, BUTTON_NONE
},
98 {PF_BACK
, BUTTON_BOTTOMRIGHT
, BUTTON_NONE
},
100 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
101 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
102 #elif CONFIG_KEYPAD == SANSA_C100_PAD
103 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
104 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
105 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
106 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
107 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
108 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
109 CONFIG_KEYPAD == SANSA_FUZE_PAD
110 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
111 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
113 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
114 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
115 {PF_QUIT
, BUTTON_POWER
|BUTTON_REPEAT
, BUTTON_POWER
},
116 #if CONFIG_KEYPAD == COWOND2_PAD
117 {PF_BACK
, BUTTON_POWER
|BUTTON_REL
, BUTTON_POWER
},
118 {ACTION_NONE
, BUTTON_POWER
, BUTTON_NONE
},
120 #elif CONFIG_KEYPAD == SANSA_E200_PAD
121 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
122 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
123 {PF_QUIT
, BUTTON_EQ
, BUTTON_NONE
},
124 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
125 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
126 || (CONFIG_KEYPAD == IPOD_4G_PAD)
127 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
128 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
129 {PF_QUIT
, BUTTON_POWERPLAY
|BUTTON_REPEAT
, BUTTON_POWERPLAY
},
130 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
131 {PF_QUIT
, BUTTON_RC_REC
, BUTTON_NONE
},
132 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
133 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
134 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
135 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
136 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
138 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
139 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
|CONTEXT_REMOTE
)
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
)
144 const struct button_mapping
*pf_contexts
[] =
146 pf_context_album_scroll
,
152 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
153 #else /* LCD_DEPTH <= 1 */
154 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
155 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
156 #define PICTUREFLOW_DRMODE DRMODE_SOLID
158 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
160 #endif /* LCD_DEPTH <= 1 */
163 #define LCD_BUF _grey_info.buffer
164 #define MYLCD(fn) grey_ ## fn
165 #define G_PIX(r,g,b) \
166 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
167 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
168 #define G_BRIGHT(y) (y)
169 #define BUFFER_WIDTH _grey_info.width
170 #define BUFFER_HEIGHT _grey_info.height
171 typedef unsigned char pix_t
;
172 #else /* LCD_DEPTH >= 8 */
173 #define LCD_BUF rb->lcd_framebuffer
174 #define MYLCD(fn) rb->lcd_ ## fn
175 #define G_PIX LCD_RGBPACK
176 #define N_PIX LCD_RGBPACK
177 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
178 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
179 #define BUFFER_WIDTH LCD_WIDTH
180 #define BUFFER_HEIGHT LCD_HEIGHT
181 typedef fb_data pix_t
;
182 #endif /* LCD_DEPTH >= 8 */
184 /* for fixed-point arithmetic, we need minimum 32-bit long
185 long long (64-bit) might be useful for multiplication and division */
187 #define PFREAL_SHIFT 10
188 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
189 #define PFREAL_ONE (1 << PFREAL_SHIFT)
190 #define PFREAL_HALF (PFREAL_ONE >> 1)
193 #define IANGLE_MAX 1024
194 #define IANGLE_MASK 1023
196 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
197 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
198 #define DISPLAY_HEIGHT REFLECT_TOP
199 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
200 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
201 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
202 (REFLECT_HEIGHT * 5))
203 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
204 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
205 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
206 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
207 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
209 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
211 #define MAX_SLIDES_COUNT 10
213 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
214 #define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
217 #define EV_WAKEUP 1337
219 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
220 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
221 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
223 /* Error return values */
224 #define ERROR_NO_ALBUMS -1
225 #define ERROR_BUFFER_FULL -2
227 /* current version for cover cache */
228 #define CACHE_VERSION 3
229 #define CONFIG_VERSION 1
230 #define CONFIG_FILE "pictureflow.cfg"
232 /** structs we use */
243 int index
; /* index of the cached slide */
244 int hid
; /* handle ID of the cached slide */
245 short next
; /* "next" slide, with LRU last */
246 short prev
; /* "previous" slide */
267 struct load_slide_event_data
{
273 struct pfraw_header
{
274 int32_t width
; /* bmap width in pixels */
275 int32_t height
; /* bmap height in pixels */
278 enum show_album_name_values
{ album_name_hide
= 0, album_name_bottom
,
280 static char* show_album_name_conf
[] =
287 #define MAX_SPACING 40
288 #define MAX_MARGIN 80
290 /* config values and their defaults */
291 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
292 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
293 static int num_slides
= 4;
294 static int zoom
= 100;
295 static bool show_fps
= false;
296 static bool resize
= true;
297 static int cache_version
= 0;
298 static int show_album_name
= (LCD_HEIGHT
> 100)
299 ? album_name_top
: album_name_bottom
;
301 static struct configdata config
[] =
303 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
305 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
307 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
309 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
310 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
311 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
312 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
313 { TYPE_ENUM
, 0, 2, { .int_p
= &show_album_name
}, "show album name",
314 show_album_name_conf
}
317 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
319 /** below we allocate the memory we want to use **/
321 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
322 static uint8_t reflect_table
[REFLECT_HEIGHT
];
323 static struct slide_data center_slide
;
324 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
325 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
326 static int slide_frame
;
330 static int center_index
= 0; /* index of the slide that is in the center */
332 static PFreal offsetX
;
333 static PFreal offsetY
;
334 static int number_of_slides
;
336 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
337 static int cache_free
;
338 static int cache_used
= -1;
339 static int cache_left_index
= -1;
340 static int cache_right_index
= -1;
341 static int cache_center_index
= -1;
343 /* use long for aligning */
344 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
345 /* queue (as array) for scheduling load_surface */
347 static int empty_slide_hid
;
349 unsigned int thread_id
;
350 struct event_queue thread_q
;
352 static struct tagcache_search tcs
;
354 static struct buflib_context buf_ctx
;
356 static struct album_data
*album
;
357 static char *album_names
;
358 static int album_count
;
360 static struct track_data
*tracks
;
361 static char *track_names
;
362 static size_t borrowed
= 0;
363 static int track_count
;
364 static int track_index
;
365 static int selected_track
;
366 static int selected_track_pulse
;
367 void reset_track_list(void);
372 static bool thread_is_running
;
374 static int cover_animation_keyframe
;
375 static int extra_fade
;
377 static int albumtxt_x
= 0;
378 static int albumtxt_dir
= -1;
379 static int prev_center_index
= -1;
381 static int start_index_track_list
= 0;
382 static int track_list_visible_entries
= 0;
383 static int track_list_y
;
384 static int track_list_h
;
385 static int track_scroll_index
= 0;
386 static int track_scroll_dir
= 1;
389 Proposals for transitions:
391 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
392 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
394 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
396 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
399 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
400 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
402 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
415 static bool free_slide_prio(int prio
);
416 static inline unsigned fade_color(pix_t c
, unsigned a
);
417 bool save_pfraw(char* filename
, struct bitmap
*bm
);
418 bool load_new_slide(void);
419 int load_surface(int);
421 static inline PFreal
fmul(PFreal a
, PFreal b
)
423 return (a
*b
) >> PFREAL_SHIFT
;
427 * This version preshifts each operand, which is useful when we know how many
428 * of the least significant bits will be empty, or are worried about overflow
429 * in a particular calculation
431 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
433 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
436 /* ARMv5+ has a clz instruction equivalent to our function.
438 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
439 static inline int clz(uint32_t v
)
441 return __builtin_clz(v
);
444 /* Otherwise, use our clz, which can be inlined */
445 #elif defined(CPU_COLDFIRE)
446 /* This clz is based on the log2(n) implementation at
447 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
448 * A clz benchmark plugin showed this to be about 14% faster on coldfire
449 * than the LUT-based version.
451 static inline int clz(uint32_t v
)
483 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
484 0, 0, 0, 0, 0, 0, 0, 0 };
485 /* This clz is based on the log2(n) implementation at
486 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
487 * It is not any faster than the one above, but trades 16B in the lookup table
488 * for a savings of 12B per each inlined call.
490 static inline int clz(uint32_t v
)
508 return r
+ clz_lut
[v
];
512 /* Return the maximum possible left shift for a signed int32, without
515 static inline int allowed_shift(int32_t val
)
517 uint32_t uval
= val
^ (val
>> 31);
518 return clz(uval
) - 1;
521 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
522 * num and den before dividing.
524 static inline PFreal
fdiv(PFreal num
, PFreal den
)
526 int shift
= allowed_shift(num
);
527 shift
= MIN(PFREAL_SHIFT
, shift
);
529 den
>>= PFREAL_SHIFT
- shift
;
533 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
534 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
535 #define fabs(a) (a < 0 ? -a : a)
536 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
538 #if CONFIG_CPU == SH7034
539 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
540 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
542 #define MULUQ(a, b) ((a) * (b))
547 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
548 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
550 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
551 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
553 static inline PFreal
fmul(PFreal a
, PFreal b
)
555 return (a
*b
) >> PFREAL_SHIFT
;
558 static inline PFreal
fdiv(PFreal n
, PFreal m
)
560 return (n
<<(PFREAL_SHIFT
))/m
;
564 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
565 static const short sin_tab
[] = {
566 0, 100, 200, 297, 392, 483, 569, 650,
567 724, 792, 851, 903, 946, 980, 1004, 1019,
568 1024, 1019, 1004, 980, 946, 903, 851, 792,
569 724, 650, 569, 483, 392, 297, 200, 100,
570 0, -100, -200, -297, -392, -483, -569, -650,
571 -724, -792, -851, -903, -946, -980, -1004, -1019,
572 -1024, -1019, -1004, -980, -946, -903, -851, -792,
573 -724, -650, -569, -483, -392, -297, -200, -100,
577 static inline PFreal
fsin(int iangle
)
579 iangle
&= IANGLE_MASK
;
581 int i
= (iangle
>> 4);
582 PFreal p
= sin_tab
[i
];
583 PFreal q
= sin_tab
[(i
+1)];
585 return p
+ g
* (iangle
-i
*16)/16;
588 static inline PFreal
fcos(int iangle
)
590 return fsin(iangle
+ (IANGLE_MAX
>> 2));
593 static inline unsigned scale_val(unsigned val
, unsigned bits
)
595 val
= val
* ((1 << bits
) - 1);
596 return ((val
>> 8) + val
+ 128) >> 8;
599 static void output_row_8_transposed(uint32_t row
, void * row_in
,
600 struct scaler_context
*ctx
)
602 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
603 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
605 uint8_t *qp
= (uint8_t*)row_in
;
606 for (; dest
< end
; dest
+= ctx
->bm
->height
)
609 struct uint8_rgb
*qp
= (struct uint8_rgb
*)row_in
;
611 for (; dest
< end
; dest
+= ctx
->bm
->height
)
613 r
= scale_val(qp
->red
, 5);
614 g
= scale_val(qp
->green
, 6);
615 b
= scale_val((qp
++)->blue
, 5);
616 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
621 static void output_row_32_transposed(uint32_t row
, void * row_in
,
622 struct scaler_context
*ctx
)
624 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
625 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
627 uint32_t *qp
= (uint32_t*)row_in
;
628 for (; dest
< end
; dest
+= ctx
->bm
->height
)
629 *dest
= SC_OUT(*qp
++, ctx
);
631 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
633 for (; dest
< end
; dest
+= ctx
->bm
->height
)
635 r
= scale_val(SC_OUT(qp
->r
, ctx
), 5);
636 g
= scale_val(SC_OUT(qp
->g
, ctx
), 6);
637 b
= scale_val(SC_OUT(qp
->b
, ctx
), 5);
639 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
644 #ifdef HAVE_LCD_COLOR
645 static void output_row_32_transposed_fromyuv(uint32_t row
, void * row_in
,
646 struct scaler_context
*ctx
)
648 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
649 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
650 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
651 for (; dest
< end
; dest
+= ctx
->bm
->height
)
653 unsigned r
, g
, b
, y
, u
, v
;
654 y
= SC_OUT(qp
->b
, ctx
);
655 u
= SC_OUT(qp
->g
, ctx
);
656 v
= SC_OUT(qp
->r
, ctx
);
658 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
662 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
667 static unsigned int get_size(struct bitmap
*bm
)
669 return bm
->width
* bm
->height
* sizeof(pix_t
);
672 const struct custom_format format_transposed
= {
673 .output_row_8
= output_row_8_transposed
,
674 #ifdef HAVE_LCD_COLOR
676 output_row_32_transposed
,
677 output_row_32_transposed_fromyuv
680 .output_row_32
= output_row_32_transposed
,
685 static const struct button_mapping
* get_context_map(int context
)
687 return pf_contexts
[context
& ~CONTEXT_CUSTOM
];
690 /* Create the lookup table with the scaling values for the reflections */
691 void init_reflect_table(void)
694 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
696 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
697 (5 * REFLECT_HEIGHT
);
701 Create an index of all albums from the database.
702 Also store the album names so we can access them later.
704 int create_album_index(void)
706 album
= ((struct album_data
*)(buf_size
+ (char *) buf
)) - 1;
707 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
709 rb
->tagcache_search(&tcs
, tag_album
);
710 unsigned int l
, old_l
= 0;
712 album
[0].name_idx
= 0;
713 while (rb
->tagcache_get_next(&tcs
))
715 buf_size
-= sizeof(struct album_data
);
717 if ( album_count
> 0 )
718 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
721 /* not enough memory */
722 return ERROR_BUFFER_FULL
;
724 rb
->strcpy(buf
, tcs
.result
);
726 buf
= l
+ (char *)buf
;
727 DEBUGF("%lX: %s\n", tcs
.idxfd
[tag_album
] ? rb
->lseek(tcs
.idxfd
[tag_album
], 0, SEEK_CUR
) : -1, tcs
.result
);
728 album
[-album_count
].seek
= tcs
.result_seek
;
732 rb
->tagcache_search_finish(&tcs
);
733 ALIGN_BUFFER(buf
, buf_size
, 4);
735 struct album_data
* tmp_album
= (struct album_data
*)buf
;
736 for (i
= album_count
- 1; i
>= 0; i
--)
737 tmp_album
[i
] = album
[-i
];
739 buf
= album
+ album_count
;
740 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
744 Return a pointer to the album name of the given slide_index
746 char* get_album_name(const int slide_index
)
748 return album_names
+ album
[slide_index
].name_idx
;
752 Return a pointer to the track name of the active album
753 create_track_index has to be called first.
755 char* get_track_name(const int track_index
)
757 if ( track_index
< track_count
)
758 return track_names
+ tracks
[track_index
].name_idx
;
763 Compare two unsigned ints passed via pointers.
765 int compare_tracks (const void *a_v
, const void *b_v
)
767 uint32_t a
= ((struct track_data
*)a_v
)->sort
;
768 uint32_t b
= ((struct track_data
*)b_v
)->sort
;
773 Create the track index of the given slide_index.
775 void create_track_index(const int slide_index
)
777 if ( slide_index
== track_index
)
779 track_index
= slide_index
;
781 if (!rb
->tagcache_search(&tcs
, tag_title
))
784 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
786 int string_index
= 0, track_num
;
789 track_names
= (char *)buflib_buffer_out(&buf_ctx
, &out
);
791 int avail
= borrowed
;
792 tracks
= (struct track_data
*)(track_names
+ borrowed
);
793 while (rb
->tagcache_get_next(&tcs
))
795 avail
-= sizeof(struct track_data
);
796 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
) - 1;
797 disc_num
= rb
->tagcache_get_numeric(&tcs
, tag_discnumber
);
805 len
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
806 "%d.%02d: %s", disc_num
, track_num
+ 1, tcs
.result
);
808 len
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
809 "%d: %s", track_num
+ 1, tcs
.result
);
814 len
= tcs
.result_len
;
815 rb
->strncpy(track_names
+ string_index
, tcs
.result
, avail
);
821 if (!free_slide_prio(0))
824 buflib_buffer_out(&buf_ctx
, &out
);
829 struct track_data
*new_tracks
= (struct track_data
*)(out
+ (uintptr_t)tracks
);
830 unsigned int bytes
= track_count
* sizeof(struct track_data
);
831 rb
->memmove(new_tracks
, tracks
, bytes
);
840 tracks
->sort
= ((disc_num
- 1) << 24) + (track_num
<< 14) + track_count
;
841 tracks
->name_idx
= string_index
;
842 tracks
->seek
= tcs
.result_seek
;
847 rb
->tagcache_search_finish(&tcs
);
849 /* now fix the track list order */
850 rb
->qsort(tracks
, track_count
, sizeof(struct track_data
), compare_tracks
);
858 Determine filename of the album art for the given slide_index and
859 store the result in buf.
860 The algorithm looks for the first track of the given album uses
861 find_albumart to find the filename.
863 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
866 if ( slide_index
== -1 )
868 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
871 if (!rb
->tagcache_search(&tcs
, tag_filename
))
875 /* find the first track of the album */
876 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
878 if ( rb
->tagcache_get_next(&tcs
) ) {
882 fd
= rb
->open(tcs
.result
, O_RDONLY
);
883 rb
->get_metadata(&id3
, fd
, tcs
.result
);
885 if ( search_albumart_files(&id3
, "", buf
, buflen
) )
891 /* did not find a matching track */
894 rb
->tagcache_search_finish(&tcs
);
899 Draw the PictureFlow logo
901 void draw_splashscreen(void)
903 unsigned char * buf_tmp
= buf
;
904 size_t buf_tmp_size
= buf_size
;
905 struct screen
* display
= rb
->screens
[0];
907 ALIGN_BUFFER(buf_tmp
, buf_tmp_size
, sizeof(fb_data
));
909 struct bitmap logo
= {
919 int ret
= rb
->read_bmp_file(SPLASH_BMP
, &logo
, buf_tmp_size
, FORMAT_NATIVE
,
922 rb
->lcd_set_background(N_BRIGHT(0));
923 rb
->lcd_set_foreground(N_BRIGHT(255));
925 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
927 rb
->lcd_clear_display();
931 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
932 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
934 display
->bitmap(logo
.data
, (LCD_WIDTH
- logo
.width
) / 2, 10,
935 logo
.width
, logo
.height
);
936 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
937 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
946 Draw a simple progress bar
948 void draw_progressbar(int step
)
951 const int bar_height
= 22;
952 const int w
= LCD_WIDTH
- 20;
955 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
957 int y
= (LCD_HEIGHT
- txt_h
)/2;
959 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
963 rb
->lcd_set_foreground(N_BRIGHT(100));
965 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
967 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
970 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
972 rb
->lcd_set_foreground(N_BRIGHT(255));
979 Precomupte the album art images and store them in CACHE_PREFIX.
981 bool create_albumart_cache(void)
986 struct bitmap input_bmp
;
988 char pfraw_file
[MAX_PATH
];
989 char albumart_file
[MAX_PATH
];
990 unsigned int format
= FORMAT_NATIVE
;
992 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
994 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
995 for (i
=0; i
< album_count
; i
++)
997 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
999 /* delete existing cache, so it's a true rebuild */
1000 if(rb
->file_exists(pfraw_file
))
1001 rb
->remove(pfraw_file
);
1002 draw_progressbar(i
);
1003 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
1006 input_bmp
.data
= buf
;
1007 input_bmp
.width
= DISPLAY_WIDTH
;
1008 input_bmp
.height
= DISPLAY_HEIGHT
;
1009 ret
= read_image_file(albumart_file
, &input_bmp
,
1010 buf_size
, format
, &format_transposed
);
1012 rb
->splash(HZ
, "Could not read bmp");
1013 continue; /* skip missing/broken files */
1015 if (!save_pfraw(pfraw_file
, &input_bmp
))
1017 rb
->splash(HZ
, "Could not write bmp");
1020 if ( rb
->button_get(false) == PF_MENU
) return false;
1022 if ( slides
== 0 ) {
1023 /* Warn the user that we couldn't find any albumart */
1024 rb
->splash(2*HZ
, "No album art found");
1031 Thread used for loading and preparing bitmaps in the background
1035 long sleep_time
= 5 * HZ
;
1036 struct queue_event ev
;
1038 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1043 /* we just woke up */
1046 while ( load_new_slide() ) {
1058 End the thread by posting the EV_EXIT event
1060 void end_pf_thread(void)
1062 if ( thread_is_running
) {
1063 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
1064 rb
->thread_wait(thread_id
);
1065 /* remove the thread's queue from the broadcast list */
1066 rb
->queue_delete(&thread_q
);
1067 thread_is_running
= false;
1074 Create the thread an setup the event queue
1076 bool create_pf_thread(void)
1078 /* put the thread's queue in the bcast list */
1079 rb
->queue_init(&thread_q
, true);
1080 if ((thread_id
= rb
->create_thread(
1083 sizeof(thread_stack
),
1085 "Picture load thread"
1086 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1087 PRIORITY_REALTIME
+ 1))
1093 thread_is_running
= true;
1094 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1099 Safe the given bitmap as filename in the pfraw format
1101 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1103 struct pfraw_header bmph
;
1104 bmph
.width
= bm
->width
;
1105 bmph
.height
= bm
->height
;
1106 int fh
= rb
->creat( filename
);
1107 if( fh
< 0 ) return false;
1108 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1110 for( y
= 0; y
< bm
->height
; y
++ )
1112 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
1113 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
1121 * The following functions implement the linked-list-in-array used to manage
1122 * the LRU cache of slides, and the list of free cache slots.
1125 #define seek_right_while(start, cond) \
1127 int ind_, next_ = (start); \
1130 next_ = cache[ind_].next; \
1131 } while (next_ != cache_used && (cond)); \
1135 #define seek_left_while(start, cond) \
1137 int ind_, next_ = (start); \
1140 next_ = cache[ind_].prev; \
1141 } while (ind_ != cache_used && (cond)); \
1146 Pop the given item from the linked list starting at *head, returning the next
1147 item, or -1 if the list is now empty.
1149 static inline int lla_pop_item (int *head
, int i
)
1151 int prev
= cache
[i
].prev
;
1152 int next
= cache
[i
].next
;
1158 else if (i
== *head
)
1160 cache
[next
].prev
= prev
;
1161 cache
[prev
].next
= next
;
1167 Pop the head item from the list starting at *head, returning the index of the
1168 item, or -1 if the list is already empty.
1170 static inline int lla_pop_head (int *head
)
1174 lla_pop_item(head
, i
);
1179 Insert the item at index i before the one at index p.
1181 static inline void lla_insert (int i
, int p
)
1184 int prev
= cache
[next
].prev
;
1185 cache
[next
].prev
= i
;
1186 cache
[prev
].next
= i
;
1187 cache
[i
].next
= next
;
1188 cache
[i
].prev
= prev
;
1193 Insert the item at index i at the end of the list starting at *head.
1195 static inline void lla_insert_tail (int *head
, int i
)
1203 lla_insert(i
, *head
);
1207 Insert the item at index i before the one at index p.
1209 static inline void lla_insert_after(int i
, int p
)
1217 Insert the item at index i before the one at index p in the list starting at
1220 static inline void lla_insert_before(int *head
, int i
, int p
)
1229 Free the used slide at index i, and its buffer, and move it to the free
1232 static inline void free_slide(int i
)
1234 if (cache
[i
].hid
!= empty_slide_hid
)
1235 buflib_free(&buf_ctx
, cache
[i
].hid
);
1236 cache
[i
].index
= -1;
1237 lla_pop_item(&cache_used
, i
);
1238 lla_insert_tail(&cache_free
, i
);
1239 if (cache_used
== -1)
1241 cache_right_index
= -1;
1242 cache_left_index
= -1;
1243 cache_center_index
= -1;
1249 Free one slide ranked above the given priority. If no such slide can be found,
1252 static bool free_slide_prio(int prio
)
1254 if (cache_used
== -1)
1256 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1257 int prio_l
= cache
[l
].index
< center_index
?
1258 center_index
- cache
[l
].index
: 0;
1259 int prio_r
= cache
[r
].index
> center_index
?
1260 cache
[r
].index
- center_index
: 0;
1261 if (prio_l
> prio_r
)
1269 if (prio_max
> prio
)
1271 if (i
== cache_left_index
)
1272 cache_left_index
= cache
[i
].next
;
1273 if (i
== cache_right_index
)
1274 cache_right_index
= cache
[i
].prev
;
1282 Read the pfraw image given as filename and return the hid of the buffer
1284 int read_pfraw(char* filename
, int prio
)
1286 struct pfraw_header bmph
;
1287 int fh
= rb
->open(filename
, O_RDONLY
);
1289 return empty_slide_hid
;
1291 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1293 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1294 bmph
.width
* bmph
.height
;
1297 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1304 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1306 bm
->width
= bmph
.width
;
1307 bm
->height
= bmph
.height
;
1308 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1311 for( y
= 0; y
< bm
->height
; y
++ )
1313 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1322 Load the surface for the given slide_index into the cache at cache_index.
1324 static inline bool load_and_prepare_surface(const int slide_index
,
1325 const int cache_index
,
1328 char tmp_path_name
[MAX_PATH
+1];
1329 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1332 int hid
= read_pfraw(tmp_path_name
, prio
);
1336 cache
[cache_index
].hid
= hid
;
1338 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1339 cache
[cache_index
].index
= slide_index
;
1347 Load the "next" slide that we can load, freeing old slides if needed, provided
1348 that they are further from center_index than the current slide
1350 bool load_new_slide(void)
1353 if (cache_center_index
!= -1)
1356 if (cache
[cache_center_index
].index
!= center_index
)
1358 if (cache
[cache_center_index
].index
< center_index
)
1360 cache_center_index
= seek_right_while(cache_center_index
,
1361 cache
[next_
].index
<= center_index
);
1362 prev
= cache_center_index
;
1363 next
= cache
[cache_center_index
].next
;
1367 cache_center_index
= seek_left_while(cache_center_index
,
1368 cache
[next_
].index
>= center_index
);
1369 next
= cache_center_index
;
1370 prev
= cache
[cache_center_index
].prev
;
1372 if (cache
[cache_center_index
].index
!= center_index
)
1374 if (cache_free
== -1)
1376 i
= lla_pop_head(&cache_free
);
1377 if (!load_and_prepare_surface(center_index
, i
, 0))
1378 goto fail_and_refree
;
1379 if (cache
[next
].index
== -1)
1381 if (cache
[prev
].index
== -1)
1382 goto insert_first_slide
;
1384 next
= cache
[prev
].next
;
1386 lla_insert(i
, next
);
1387 if (cache
[i
].index
< cache
[cache_used
].index
)
1389 cache_center_index
= i
;
1390 cache_left_index
= i
;
1391 cache_right_index
= i
;
1395 if (cache
[cache_left_index
].index
>
1396 cache
[cache_center_index
].index
)
1397 cache_left_index
= cache_center_index
;
1398 if (cache
[cache_right_index
].index
<
1399 cache
[cache_center_index
].index
)
1400 cache_right_index
= cache_center_index
;
1401 cache_left_index
= seek_left_while(cache_left_index
,
1402 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1403 cache_right_index
= seek_right_while(cache_right_index
,
1404 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1405 int prio_l
= cache
[cache_center_index
].index
-
1406 cache
[cache_left_index
].index
+ 1;
1407 int prio_r
= cache
[cache_right_index
].index
-
1408 cache
[cache_center_index
].index
+ 1;
1409 if ((prio_l
< prio_r
||
1410 cache
[cache_right_index
].index
>= number_of_slides
) &&
1411 cache
[cache_left_index
].index
> 0)
1413 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1415 i
= lla_pop_head(&cache_free
);
1416 if (load_and_prepare_surface(cache
[cache_left_index
].index
1419 lla_insert_before(&cache_used
, i
, cache_left_index
);
1420 cache_left_index
= i
;
1423 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1425 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1427 i
= lla_pop_head(&cache_free
);
1428 if (load_and_prepare_surface(cache
[cache_right_index
].index
1431 lla_insert_after(i
, cache_right_index
);
1432 cache_right_index
= i
;
1437 i
= lla_pop_head(&cache_free
);
1438 if (load_and_prepare_surface(center_index
, i
, 0))
1443 cache_center_index
= i
;
1444 cache_left_index
= i
;
1445 cache_right_index
= i
;
1453 lla_insert_tail(&cache_free
, i
);
1460 Get a slide from the buffer
1462 static inline struct dim
*get_slide(const int hid
)
1469 bmp
= buflib_get_data(&buf_ctx
, hid
);
1476 Return the requested surface
1478 static inline struct dim
*surface(const int slide_index
)
1480 if (slide_index
< 0)
1482 if (slide_index
>= number_of_slides
)
1485 if ((i
= cache_used
) != -1)
1488 if (cache
[i
].index
== slide_index
)
1489 return get_slide(cache
[i
].hid
);
1491 } while (i
!= cache_used
);
1493 return get_slide(empty_slide_hid
);
1497 adjust slides so that they are in "steady state" position
1499 void reset_slides(void)
1501 center_slide
.angle
= 0;
1502 center_slide
.cx
= 0;
1503 center_slide
.cy
= 0;
1504 center_slide
.distance
= 0;
1505 center_slide
.slide_index
= center_index
;
1508 for (i
= 0; i
< num_slides
; i
++) {
1509 struct slide_data
*si
= &left_slides
[i
];
1511 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1513 si
->slide_index
= center_index
- 1 - i
;
1517 for (i
= 0; i
< num_slides
; i
++) {
1518 struct slide_data
*si
= &right_slides
[i
];
1520 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1522 si
->slide_index
= center_index
+ 1 + i
;
1529 Updates look-up table and other stuff necessary for the rendering.
1530 Call this when the viewport size or slide dimension is changed.
1532 * To calculate the offset that will provide the proper margin, we use the same
1533 * projection used to render the slides. The solution for xc, the slide center,
1535 * xp * (zo + xs * sin(r))
1536 * xc = xp - xs * cos(r) + ───────────────────────
1538 * TODO: support moving the side slides toward or away from the camera
1540 void recalc_offsets(void)
1542 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1544 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1545 PFREAL_ONE
) * zoom
/ 100;
1548 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1549 cosr
= fcos(-itilt
);
1550 sinr
= fsin(-itilt
);
1551 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1552 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1553 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1554 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1556 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1561 Fade the given color by spreading the fb_data (ushort)
1562 to an uint, multiply and compress the result back to a ushort.
1564 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1565 static inline unsigned fade_color(pix_t c
, unsigned a
)
1567 unsigned int result
;
1569 a
= (a
+ 2) & 0x1fc;
1570 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1571 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1573 return swap16(result
);
1575 #elif LCD_PIXELFORMAT == RGB565
1576 static inline unsigned fade_color(pix_t c
, unsigned a
)
1578 unsigned int result
;
1579 a
= (a
+ 2) & 0x1fc;
1580 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1581 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1586 static inline unsigned fade_color(pix_t c
, unsigned a
)
1589 return MULUQ(val
, a
) >> 8;
1594 * Render a single slide
1595 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1596 * on the slide from its center, zo is the slide's depth offset from the plane
1597 * of the display, r is the angle at which the slide is tilted, and xp is the
1598 * point on the display corresponding to xs on the slide, the projection
1601 * z * (xc + xs * cos(r))
1602 * xp = ──────────────────────
1603 * z + zo + xs * sin(r)
1605 * z * (xc - xp) - xp * zo
1606 * xs = ────────────────────────
1607 * xp * sin(r) - z * cos(r)
1609 * We use the xp projection once, to find the left edge of the slide on the
1610 * display. From there, we use the xs reverse projection to find the horizontal
1611 * offset from the slide center of each column on the screen, until we reach
1612 * the right edge of the slide, or the screen. The reverse projection can be
1613 * optimized by saving the numerator and denominator of the fraction, which can
1614 * then be incremented by (z + zo) and sin(r) respectively.
1616 void render_slide(struct slide_data
*slide
, const int alpha
)
1618 struct dim
*bmp
= surface(slide
->slide_index
);
1622 if (slide
->angle
> 255 || slide
->angle
< -255)
1624 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1626 const int sw
= bmp
->width
;
1627 const int sh
= bmp
->height
;
1628 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1629 const int w
= LCD_WIDTH
;
1631 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1633 if (alpha
== 256) { /* opaque -> copy table */
1634 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1635 } else { /* precalculate faded table */
1637 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1638 lalpha
= reflect_table
[i
];
1639 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1643 PFreal cosr
= fcos(slide
->angle
);
1644 PFreal sinr
= fsin(slide
->angle
);
1645 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1646 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1647 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1648 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1649 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1651 /* Since we're finding the screen position of the left edge of the slide,
1654 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1656 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1660 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1661 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1662 xs
= fdiv(xsnum
, xsden
);
1664 xsnumi
= -CAM_DIST_R
- zo
;
1667 int dy
= PFREAL_ONE
;
1668 for (x
= xi
; x
< w
; x
++) {
1669 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1672 if (zo
|| slide
->angle
)
1673 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1675 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1676 const int pixelstep
= BUFFER_WIDTH
;
1678 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1679 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1680 pix_t
*pixel
= &buffer
[((LCD_HEIGHT
/2)-1)*BUFFER_WIDTH
+ x
];
1684 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1690 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1695 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1696 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1697 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1698 p
+ (LCD_HEIGHT
/2) * dy
);
1699 pixel
= &buffer
[(LCD_HEIGHT
/2)*BUFFER_WIDTH
+ x
];
1703 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1709 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1715 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1716 int lalpha
= reftab
[ty
];
1717 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1722 if (zo
|| slide
->angle
)
1726 xs
= fdiv(xsnum
, xsden
);
1731 /* let the music play... */
1738 Jump the the given slide_index
1740 static inline void set_current_slide(const int slide_index
)
1742 int old_center_index
= center_index
;
1744 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1745 if (old_center_index
!= center_index
)
1746 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1747 target
= center_index
;
1748 slide_frame
= slide_index
<< 16;
1753 Start the animation for changing slides
1755 void start_animation(void)
1757 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1758 pf_state
= pf_scrolling
;
1762 Go to the previous slide
1764 void show_previous_slide(void)
1767 if (center_index
> 0) {
1768 target
= center_index
- 1;
1771 } else if ( step
> 0 ) {
1772 target
= center_index
;
1775 target
= fmax(0, center_index
- 2);
1781 Go to the next slide
1783 void show_next_slide(void)
1786 if (center_index
< number_of_slides
- 1) {
1787 target
= center_index
+ 1;
1790 } else if ( step
< 0 ) {
1791 target
= center_index
;
1794 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1800 Render the slides. Updates only the offscreen buffer.
1802 void render_all_slides(void)
1804 MYLCD(set_background
)(G_BRIGHT(0));
1805 /* TODO: Optimizes this by e.g. invalidating rects */
1806 MYLCD(clear_display
)();
1808 int nleft
= num_slides
;
1809 int nright
= num_slides
;
1813 /* no animation, boring plain rendering */
1814 for (index
= nleft
- 2; index
>= 0; index
--) {
1815 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1816 alpha
-= extra_fade
;
1818 render_slide(&left_slides
[index
], alpha
);
1820 for (index
= nright
- 2; index
>= 0; index
--) {
1821 int alpha
= (index
< nright
- 2) ? 256 : 128;
1822 alpha
-= extra_fade
;
1824 render_slide(&right_slides
[index
], alpha
);
1827 /* the first and last slide must fade in/fade out */
1828 for (index
= nleft
- 1; index
>= 0; index
--) {
1830 if (index
== nleft
- 1)
1831 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1832 if (index
== nleft
- 2)
1833 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1834 if (index
== nleft
- 3)
1835 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1836 render_slide(&left_slides
[index
], alpha
);
1838 for (index
= nright
- 1; index
>= 0; index
--) {
1839 int alpha
= (index
< nright
- 2) ? 256 : 128;
1840 if (index
== nright
- 1)
1841 alpha
= (step
> 0) ? fade
/ 2 : 0;
1842 if (index
== nright
- 2)
1843 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1844 if (index
== nright
- 3)
1845 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1846 render_slide(&right_slides
[index
], alpha
);
1849 render_slide(¢er_slide
, 256);
1854 Updates the animation effect. Call this periodically from a timer.
1856 void update_scroll_animation(void)
1864 /* deaccelerate when approaching the target */
1866 const int max
= 2 * 65536;
1868 int fi
= slide_frame
;
1869 fi
-= (target
<< 16);
1874 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1875 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1878 slide_frame
+= speed
* step
;
1880 int index
= slide_frame
>> 16;
1881 int pos
= slide_frame
& 0xffff;
1882 int neg
= 65536 - pos
;
1883 int tick
= (step
< 0) ? neg
: pos
;
1884 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1886 /* the leftmost and rightmost slide must fade away */
1891 if (center_index
!= index
) {
1892 center_index
= index
;
1893 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1894 slide_frame
= index
<< 16;
1895 center_slide
.slide_index
= center_index
;
1896 for (i
= 0; i
< num_slides
; i
++)
1897 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1898 for (i
= 0; i
< num_slides
; i
++)
1899 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1902 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1903 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1904 center_slide
.cy
= fmul(offsetY
, ftick
);
1906 if (center_index
== target
) {
1914 for (i
= 0; i
< num_slides
; i
++) {
1915 struct slide_data
*si
= &left_slides
[i
];
1918 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1919 * slide_spacing
* ftick
);
1923 for (i
= 0; i
< num_slides
; i
++) {
1924 struct slide_data
*si
= &right_slides
[i
];
1927 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1928 * slide_spacing
* ftick
;
1933 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1934 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1935 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1936 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1938 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1939 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1940 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1941 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1944 /* must change direction ? */
1957 void cleanup(void *parameter
)
1960 /* Turn on backlight timeout (revert to settings) */
1961 backlight_use_settings(); /* backlight control in lib/helper.c */
1969 Create the "?" slide, that is shown while loading
1970 or when no cover was found.
1972 int create_empty_slide(bool force
)
1974 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1975 struct bitmap input_bmp
;
1977 input_bmp
.width
= DISPLAY_WIDTH
;
1978 input_bmp
.height
= DISPLAY_HEIGHT
;
1980 input_bmp
.format
= FORMAT_NATIVE
;
1982 input_bmp
.data
= (char*)buf
;
1983 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1985 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1986 &format_transposed
);
1987 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1995 Shows the album name setting menu
1997 int album_name_menu(void)
1999 int selection
= show_album_name
;
2001 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
2002 "Hide album title", "Show at the bottom", "Show at the top");
2003 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
2005 show_album_name
= selection
;
2006 return GO_TO_PREVIOUS
;
2010 Shows the settings menu
2012 int settings_menu(void)
2017 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
2018 "Spacing", "Centre margin", "Number of slides", "Zoom",
2019 "Show album title", "Resize Covers", "Rebuild cache");
2022 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2025 rb
->set_bool("Show FPS", &show_fps
);
2030 rb
->set_int("Spacing between slides", "", 1,
2032 NULL
, 1, 0, 100, NULL
);
2038 rb
->set_int("Centre margin", "", 1,
2040 NULL
, 1, 0, 80, NULL
);
2046 rb
->set_int("Number of slides", "", 1, &num_slides
,
2047 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2053 rb
->set_int("Zoom", "", 1, &zoom
,
2054 NULL
, 1, 10, 300, NULL
);
2066 rb
->set_bool("Resize Covers", &resize
);
2067 if (old_val
== resize
) /* changed? */
2069 /* fallthrough if changed, since cache needs to be rebuilt */
2072 rb
->remove(EMPTY_SLIDE
);
2073 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2076 case MENU_ATTACHED_USB
:
2077 return PLUGIN_USB_CONNECTED
;
2079 } while ( selection
>= 0 );
2080 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2093 rb
->lcd_set_foreground(N_BRIGHT(255));
2096 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2097 "Settings", "Return", "Quit");
2099 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2101 result
= settings_menu();
2102 if ( result
!= 0 ) return result
;
2111 case MENU_ATTACHED_USB
:
2112 return PLUGIN_USB_CONNECTED
;
2121 Animation step for zooming into the current cover
2123 void update_cover_in_animation(void)
2125 cover_animation_keyframe
++;
2126 if( cover_animation_keyframe
< 20 ) {
2127 center_slide
.distance
-=5;
2128 center_slide
.angle
+=1;
2131 else if( cover_animation_keyframe
< 35 ) {
2132 center_slide
.angle
+=16;
2135 cover_animation_keyframe
= 0;
2136 pf_state
= pf_show_tracks
;
2141 Animation step for zooming out the current cover
2143 void update_cover_out_animation(void)
2145 cover_animation_keyframe
++;
2146 if( cover_animation_keyframe
<= 15 ) {
2147 center_slide
.angle
-=16;
2149 else if( cover_animation_keyframe
< 35 ) {
2150 center_slide
.distance
+=5;
2151 center_slide
.angle
-=1;
2155 cover_animation_keyframe
= 0;
2161 Draw a blue gradient at y with height h
2163 static inline void draw_gradient(int y
, int h
)
2165 static int r
, inc
, c
;
2166 inc
= (100 << 8) / h
;
2168 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2169 int c2
= selected_track_pulse
- 5;
2170 for (r
=0; r
<h
; r
++) {
2171 #ifdef HAVE_LCD_COLOR
2172 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2175 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2177 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2186 static void track_list_yh(int char_height
)
2188 switch (show_album_name
)
2190 case album_name_hide
:
2191 track_list_y
= (show_fps
? char_height
: 0);
2192 track_list_h
= LCD_HEIGHT
- track_list_y
;
2194 case album_name_bottom
:
2195 track_list_y
= (show_fps
? char_height
: 0);
2196 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2198 default: /* case album_name_top */
2199 track_list_y
= char_height
* 2;
2200 track_list_h
= LCD_HEIGHT
- track_list_y
-
2201 (show_fps
? char_height
: 0);
2207 Reset the track list after a album change
2209 void reset_track_list(void)
2211 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2212 track_list_yh(albumtxt_h
);
2213 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2214 start_index_track_list
= 0;
2215 track_scroll_index
= 0;
2216 track_scroll_dir
= 1;
2219 /* let the tracklist start more centered
2220 * if the screen isn't filled with tracks */
2221 if (track_count
*albumtxt_h
< track_list_h
)
2223 track_list_h
= track_count
* albumtxt_h
;
2224 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2229 Display the list of tracks
2231 void show_track_list(void)
2233 MYLCD(clear_display
)();
2234 if ( center_slide
.slide_index
!= track_index
) {
2235 create_track_index(center_slide
.slide_index
);
2238 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2239 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2241 int titletxt_y
= track_list_y
;
2243 track_i
= start_index_track_list
;
2244 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2247 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2248 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2249 if ( track_i
== selected_track
) {
2250 draw_gradient(titletxt_y
, titletxt_h
);
2251 MYLCD(set_foreground
)(G_BRIGHT(255));
2252 if (titletxt_w
> LCD_WIDTH
) {
2253 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2254 track_scroll_dir
= 1;
2255 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2256 track_scroll_index
+= track_scroll_dir
*2;
2257 titletxt_x
= track_scroll_index
;
2259 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2262 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2263 MYLCD(set_foreground
)(G_BRIGHT(color
));
2264 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2266 titletxt_y
+= titletxt_h
;
2270 void select_next_track(void)
2272 if ( selected_track
< track_count
- 1 ) {
2274 track_scroll_index
= 0;
2275 track_scroll_dir
= 1;
2276 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2277 start_index_track_list
++;
2281 void select_prev_track(void)
2283 if (selected_track
> 0 ) {
2284 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2285 track_scroll_index
= 0;
2286 track_scroll_dir
= 1;
2292 Draw the current album name
2294 void draw_album_text(void)
2296 if (0 == show_album_name
)
2299 int albumtxt_w
, albumtxt_h
;
2304 /* Draw album text */
2305 if ( pf_state
== pf_scrolling
) {
2306 c
= ((slide_frame
& 0xffff )/ 255);
2307 if (step
< 0) c
= 255-c
;
2308 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2309 albumtxt
= get_album_name(center_index
+step
);
2313 albumtxt
= get_album_name(center_index
);
2319 albumtxt
= get_album_name(center_index
);
2322 MYLCD(set_foreground
)(G_BRIGHT(c
));
2323 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2324 if (center_index
!= prev_center_index
) {
2327 prev_center_index
= center_index
;
2330 if (show_album_name
== album_name_top
)
2331 albumtxt_y
= albumtxt_h
/ 2;
2333 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2335 if (albumtxt_w
> LCD_WIDTH
) {
2336 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2337 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2338 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2339 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2340 albumtxt_x
+= albumtxt_dir
;
2344 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2352 Main function that also contain the main plasma
2359 rb
->lcd_setfont(FONT_UI
);
2360 draw_splashscreen();
2362 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2363 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2364 rb
->splash(HZ
, "Could not create directory " CACHE_PREFIX
);
2365 return PLUGIN_ERROR
;
2369 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2371 init_reflect_table();
2373 ALIGN_BUFFER(buf
, buf_size
, 4);
2374 ret
= create_album_index();
2375 if (ret
== ERROR_BUFFER_FULL
) {
2376 rb
->splash(HZ
, "Not enough memory for album names");
2377 return PLUGIN_ERROR
;
2378 } else if (ret
== ERROR_NO_ALBUMS
) {
2379 rb
->splash(HZ
, "No albums found. Please enable database");
2380 return PLUGIN_ERROR
;
2383 ALIGN_BUFFER(buf
, buf_size
, 4);
2384 number_of_slides
= album_count
;
2385 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2386 rb
->splash(HZ
, "Could not create album art cache");
2387 return PLUGIN_ERROR
;
2390 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2391 rb
->splash(HZ
, "Could not load the empty slide");
2392 return PLUGIN_ERROR
;
2394 cache_version
= CACHE_VERSION
;
2395 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2400 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2401 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2403 rb
->splash(HZ
, "Greylib init failed!");
2404 return PLUGIN_ERROR
;
2406 grey_setfont(FONT_UI
);
2407 buf_size
-= grey_buf_used
;
2408 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2410 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2412 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2414 rb
->splash(HZ
, "Unable to load empty slide image");
2415 return PLUGIN_ERROR
;
2418 if (!create_pf_thread()) {
2419 rb
->splash(HZ
, "Cannot create thread!");
2420 return PLUGIN_ERROR
;
2426 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2429 cache
[i
].next
= i
+ 1;
2430 cache
[i
].prev
= i
- 1;
2432 cache
[0].prev
= i
- 1;
2433 cache
[i
- 1].next
= 0;
2453 long last_update
= *rb
->current_tick
;
2454 long current_update
;
2455 long update_interval
= 100;
2459 bool instant_update
;
2462 grey_set_drawmode(DRMODE_FG
);
2464 rb
->lcd_set_drawmode(DRMODE_FG
);
2466 current_update
= *rb
->current_tick
;
2469 /* Initial rendering */
2470 instant_update
= false;
2473 switch ( pf_state
) {
2475 update_scroll_animation();
2476 render_all_slides();
2477 instant_update
= true;
2480 update_cover_in_animation();
2481 render_all_slides();
2482 instant_update
= true;
2485 update_cover_out_animation();
2486 render_all_slides();
2487 instant_update
= true;
2489 case pf_show_tracks
:
2493 render_all_slides();
2498 if (current_update
- last_update
> update_interval
) {
2499 fps
= frames
* HZ
/ (current_update
- last_update
);
2500 last_update
= current_update
;
2507 MYLCD(set_foreground
)(G_BRIGHT(255));
2509 MYLCD(set_foreground
)(G_PIX(255,0,0));
2511 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2512 if (show_album_name
== album_name_top
)
2513 fpstxt_y
= LCD_HEIGHT
-
2514 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2517 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2522 /* Copy offscreen buffer to LCD and give time to other threads */
2526 /*/ Handle buttons */
2527 button
= rb
->get_custom_action(CONTEXT_CUSTOM
|
2528 (pf_state
== pf_show_tracks
? 1 : 0),
2529 instant_update
? 0 : HZ
/16,
2537 if ( pf_state
== pf_show_tracks
)
2539 buflib_buffer_in(&buf_ctx
, borrowed
);
2542 pf_state
= pf_cover_out
;
2544 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2553 if ( ret
== -1 ) return PLUGIN_OK
;
2554 if ( ret
!= 0 ) return ret
;
2558 MYLCD(set_drawmode
)(DRMODE_FG
);
2562 case PF_NEXT_REPEAT
:
2563 if ( pf_state
== pf_show_tracks
)
2564 select_next_track();
2565 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2570 case PF_PREV_REPEAT
:
2571 if ( pf_state
== pf_show_tracks
)
2572 select_prev_track();
2573 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2574 show_previous_slide();
2578 if ( pf_state
== pf_idle
) {
2579 pf_state
= pf_cover_in
;
2584 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2585 == SYS_USB_CONNECTED
)
2586 return PLUGIN_USB_CONNECTED
;
2594 /*************************** Plugin entry point ****************************/
2596 enum plugin_status
plugin_start(const void *parameter
)
2601 rb
->lcd_set_backdrop(NULL
);
2603 /* Turn off backlight timeout */
2604 backlight_force_on(); /* backlight control in lib/helper.c */
2605 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2606 rb
->cpu_boost(true);
2608 #if PLUGIN_BUFFER_SIZE > 0x10000
2609 buf
= rb
->plugin_get_buffer(&buf_size
);
2611 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2614 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2615 rb
->cpu_boost(false);
2617 if ( ret
== PLUGIN_OK
) {
2618 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2621 rb
->splash(HZ
, "Error writing config.");