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 == LOGIK_DAX_PAD || CONFIG_KEYPAD == MEIZU_M6SL_PAD
68 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
69 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
70 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
71 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
72 #elif CONFIG_KEYPAD == ONDIO_PAD
73 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
74 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
75 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
76 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
77 {PF_SELECT
, BUTTON_UP
|BUTTON_REL
, BUTTON_UP
},
78 {PF_CONTEXT
, BUTTON_UP
|BUTTON_REPEAT
, BUTTON_UP
},
79 {ACTION_NONE
, BUTTON_UP
, BUTTON_NONE
},
80 {ACTION_NONE
, BUTTON_DOWN
, BUTTON_NONE
},
81 {ACTION_NONE
, BUTTON_DOWN
|BUTTON_REPEAT
, BUTTON_NONE
},
82 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REL
, BUTTON_RIGHT
},
83 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
84 {PF_PREV
, BUTTON_RC_REW
, BUTTON_NONE
},
85 {PF_PREV_REPEAT
, BUTTON_RC_REW
|BUTTON_REPEAT
,BUTTON_NONE
},
86 {PF_NEXT
, BUTTON_RC_FF
, BUTTON_NONE
},
87 {PF_NEXT_REPEAT
, BUTTON_RC_FF
|BUTTON_REPEAT
, BUTTON_NONE
},
89 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM
|1)
92 const struct button_mapping pf_context_buttons
[] =
94 #ifdef HAVE_TOUCHSCREEN
95 {PF_SELECT
, BUTTON_CENTER
, BUTTON_NONE
},
96 {PF_MENU
, BUTTON_TOPLEFT
, BUTTON_NONE
},
97 {PF_BACK
, BUTTON_BOTTOMRIGHT
, BUTTON_NONE
},
99 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
100 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
101 #elif CONFIG_KEYPAD == SANSA_C100_PAD
102 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
103 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
104 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
105 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
106 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
107 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
108 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
109 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
110 {PF_QUIT
, BUTTON_HOME
|BUTTON_REPEAT
, 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 */
256 int name_idx
; /* offset to the track name */
258 int filename_idx
; /* offset to the filename in the string */
268 struct load_slide_event_data
{
274 struct pfraw_header
{
275 int32_t width
; /* bmap width in pixels */
276 int32_t height
; /* bmap height in pixels */
279 enum show_album_name_values
{ album_name_hide
= 0, album_name_bottom
,
281 static char* show_album_name_conf
[] =
288 #define MAX_SPACING 40
289 #define MAX_MARGIN 80
291 /* config values and their defaults */
292 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
293 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
294 static int num_slides
= 4;
295 static int zoom
= 100;
296 static bool show_fps
= false;
297 static bool resize
= true;
298 static int cache_version
= 0;
299 static int show_album_name
= (LCD_HEIGHT
> 100)
300 ? album_name_top
: album_name_bottom
;
302 static struct configdata config
[] =
304 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
306 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
308 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
310 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
311 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
312 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
313 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
314 { TYPE_ENUM
, 0, 2, { .int_p
= &show_album_name
}, "show album name",
315 show_album_name_conf
}
318 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
320 /** below we allocate the memory we want to use **/
322 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
323 static uint8_t reflect_table
[REFLECT_HEIGHT
];
324 static struct slide_data center_slide
;
325 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
326 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
327 static int slide_frame
;
331 static int center_index
= 0; /* index of the slide that is in the center */
333 static PFreal offsetX
;
334 static PFreal offsetY
;
335 static int number_of_slides
;
337 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
338 static int cache_free
;
339 static int cache_used
= -1;
340 static int cache_left_index
= -1;
341 static int cache_right_index
= -1;
342 static int cache_center_index
= -1;
344 /* use long for aligning */
345 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
346 /* queue (as array) for scheduling load_surface */
348 static int empty_slide_hid
;
350 unsigned int thread_id
;
351 struct event_queue thread_q
;
353 static struct tagcache_search tcs
;
355 static struct buflib_context buf_ctx
;
357 static struct album_data
*album
;
358 static char *album_names
;
359 static int album_count
;
361 static struct track_data
*tracks
;
362 static char *track_names
;
363 static size_t borrowed
= 0;
364 static int track_count
;
365 static int track_index
;
366 static int selected_track
;
367 static int selected_track_pulse
;
368 void reset_track_list(void);
373 static bool thread_is_running
;
375 static int cover_animation_keyframe
;
376 static int extra_fade
;
378 static int albumtxt_x
= 0;
379 static int albumtxt_dir
= -1;
380 static int prev_center_index
= -1;
382 static int start_index_track_list
= 0;
383 static int track_list_visible_entries
= 0;
384 static int track_list_y
;
385 static int track_list_h
;
386 static int track_scroll_index
= 0;
387 static int track_scroll_dir
= 1;
390 Proposals for transitions:
392 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
393 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
395 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
397 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
400 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
401 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
403 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
416 static bool free_slide_prio(int prio
);
417 static inline unsigned fade_color(pix_t c
, unsigned a
);
418 bool save_pfraw(char* filename
, struct bitmap
*bm
);
419 bool load_new_slide(void);
420 int load_surface(int);
422 static inline PFreal
fmul(PFreal a
, PFreal b
)
424 return (a
*b
) >> PFREAL_SHIFT
;
428 * This version preshifts each operand, which is useful when we know how many
429 * of the least significant bits will be empty, or are worried about overflow
430 * in a particular calculation
432 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
434 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
437 /* ARMv5+ has a clz instruction equivalent to our function.
439 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
440 static inline int clz(uint32_t v
)
442 return __builtin_clz(v
);
445 /* Otherwise, use our clz, which can be inlined */
446 #elif defined(CPU_COLDFIRE)
447 /* This clz is based on the log2(n) implementation at
448 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
449 * A clz benchmark plugin showed this to be about 14% faster on coldfire
450 * than the LUT-based version.
452 static inline int clz(uint32_t v
)
484 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
485 0, 0, 0, 0, 0, 0, 0, 0 };
486 /* This clz is based on the log2(n) implementation at
487 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
488 * It is not any faster than the one above, but trades 16B in the lookup table
489 * for a savings of 12B per each inlined call.
491 static inline int clz(uint32_t v
)
509 return r
+ clz_lut
[v
];
513 /* Return the maximum possible left shift for a signed int32, without
516 static inline int allowed_shift(int32_t val
)
518 uint32_t uval
= val
^ (val
>> 31);
519 return clz(uval
) - 1;
522 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
523 * num and den before dividing.
525 static inline PFreal
fdiv(PFreal num
, PFreal den
)
527 int shift
= allowed_shift(num
);
528 shift
= MIN(PFREAL_SHIFT
, shift
);
530 den
>>= PFREAL_SHIFT
- shift
;
534 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
535 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
536 #define fabs(a) (a < 0 ? -a : a)
537 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
539 #if CONFIG_CPU == SH7034
540 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
541 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
543 #define MULUQ(a, b) ((a) * (b))
548 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
549 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
551 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
552 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
554 static inline PFreal
fmul(PFreal a
, PFreal b
)
556 return (a
*b
) >> PFREAL_SHIFT
;
559 static inline PFreal
fdiv(PFreal n
, PFreal m
)
561 return (n
<<(PFREAL_SHIFT
))/m
;
565 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
566 static const short sin_tab
[] = {
567 0, 100, 200, 297, 392, 483, 569, 650,
568 724, 792, 851, 903, 946, 980, 1004, 1019,
569 1024, 1019, 1004, 980, 946, 903, 851, 792,
570 724, 650, 569, 483, 392, 297, 200, 100,
571 0, -100, -200, -297, -392, -483, -569, -650,
572 -724, -792, -851, -903, -946, -980, -1004, -1019,
573 -1024, -1019, -1004, -980, -946, -903, -851, -792,
574 -724, -650, -569, -483, -392, -297, -200, -100,
578 static inline PFreal
fsin(int iangle
)
580 iangle
&= IANGLE_MASK
;
582 int i
= (iangle
>> 4);
583 PFreal p
= sin_tab
[i
];
584 PFreal q
= sin_tab
[(i
+1)];
586 return p
+ g
* (iangle
-i
*16)/16;
589 static inline PFreal
fcos(int iangle
)
591 return fsin(iangle
+ (IANGLE_MAX
>> 2));
594 static inline unsigned scale_val(unsigned val
, unsigned bits
)
596 val
= val
* ((1 << bits
) - 1);
597 return ((val
>> 8) + val
+ 128) >> 8;
600 static void output_row_8_transposed(uint32_t row
, void * row_in
,
601 struct scaler_context
*ctx
)
603 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
604 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
606 uint8_t *qp
= (uint8_t*)row_in
;
607 for (; dest
< end
; dest
+= ctx
->bm
->height
)
610 struct uint8_rgb
*qp
= (struct uint8_rgb
*)row_in
;
612 for (; dest
< end
; dest
+= ctx
->bm
->height
)
614 r
= scale_val(qp
->red
, 5);
615 g
= scale_val(qp
->green
, 6);
616 b
= scale_val((qp
++)->blue
, 5);
617 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
622 static void output_row_32_transposed(uint32_t row
, void * row_in
,
623 struct scaler_context
*ctx
)
625 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
626 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
628 uint32_t *qp
= (uint32_t*)row_in
;
629 for (; dest
< end
; dest
+= ctx
->bm
->height
)
630 *dest
= SC_OUT(*qp
++, ctx
);
632 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
634 for (; dest
< end
; dest
+= ctx
->bm
->height
)
636 r
= scale_val(SC_OUT(qp
->r
, ctx
), 5);
637 g
= scale_val(SC_OUT(qp
->g
, ctx
), 6);
638 b
= scale_val(SC_OUT(qp
->b
, ctx
), 5);
640 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
645 #ifdef HAVE_LCD_COLOR
646 static void output_row_32_transposed_fromyuv(uint32_t row
, void * row_in
,
647 struct scaler_context
*ctx
)
649 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
650 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
651 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
652 for (; dest
< end
; dest
+= ctx
->bm
->height
)
654 unsigned r
, g
, b
, y
, u
, v
;
655 y
= SC_OUT(qp
->b
, ctx
);
656 u
= SC_OUT(qp
->g
, ctx
);
657 v
= SC_OUT(qp
->r
, ctx
);
659 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
663 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
668 static unsigned int get_size(struct bitmap
*bm
)
670 return bm
->width
* bm
->height
* sizeof(pix_t
);
673 const struct custom_format format_transposed
= {
674 .output_row_8
= output_row_8_transposed
,
675 #ifdef HAVE_LCD_COLOR
677 output_row_32_transposed
,
678 output_row_32_transposed_fromyuv
681 .output_row_32
= output_row_32_transposed
,
686 static const struct button_mapping
* get_context_map(int context
)
688 return pf_contexts
[context
& ~CONTEXT_CUSTOM
];
691 /* Create the lookup table with the scaling values for the reflections */
692 void init_reflect_table(void)
695 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
697 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
698 (5 * REFLECT_HEIGHT
);
702 Create an index of all albums from the database.
703 Also store the album names so we can access them later.
705 int create_album_index(void)
707 album
= ((struct album_data
*)(buf_size
+ (char *) buf
)) - 1;
708 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
710 rb
->tagcache_search(&tcs
, tag_album
);
711 unsigned int l
, old_l
= 0;
713 album
[0].name_idx
= 0;
714 while (rb
->tagcache_get_next(&tcs
))
716 buf_size
-= sizeof(struct album_data
);
718 if ( album_count
> 0 )
719 album
[-album_count
].name_idx
= album
[1-album_count
].name_idx
+ old_l
;
722 /* not enough memory */
723 return ERROR_BUFFER_FULL
;
725 rb
->strcpy(buf
, tcs
.result
);
727 buf
= l
+ (char *)buf
;
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
;
762 char* get_track_filename(const int track_index
)
764 if ( track_index
< track_count
)
765 return track_names
+ tracks
[track_index
].filename_idx
;
770 Compare two unsigned ints passed via pointers.
772 int compare_tracks (const void *a_v
, const void *b_v
)
774 uint32_t a
= ((struct track_data
*)a_v
)->sort
;
775 uint32_t b
= ((struct track_data
*)b_v
)->sort
;
780 Create the track index of the given slide_index.
782 void create_track_index(const int slide_index
)
784 if ( slide_index
== track_index
)
786 track_index
= slide_index
;
788 if (!rb
->tagcache_search(&tcs
, tag_title
))
791 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
793 int string_index
= 0, track_num
;
796 track_names
= (char *)buflib_buffer_out(&buf_ctx
, &out
);
798 int avail
= borrowed
;
799 tracks
= (struct track_data
*)(track_names
+ borrowed
);
800 while (rb
->tagcache_get_next(&tcs
))
802 int len
= 0, fn_idx
= 0, remain
;
804 avail
-= sizeof(struct track_data
);
805 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
) - 1;
806 disc_num
= rb
->tagcache_get_numeric(&tcs
, tag_discnumber
);
814 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
815 "%d.%02d: %s", disc_num
, track_num
+ 1, tcs
.result
);
817 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
818 "%d: %s", track_num
+ 1, tcs
.result
);
823 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
828 remain
= avail
- fn_idx
;
829 if (remain
>= MAX_PATH
)
830 { /* retrieve filename for building the playlist */
831 rb
->tagcache_retrieve(&tcs
, tcs
.idx_id
, tag_filename
,
832 track_names
+ string_index
+ fn_idx
, remain
);
833 len
= fn_idx
+ rb
->strlen(track_names
+ string_index
+ fn_idx
) + 1;
834 /* make sure track name and file name are really split by a \0, else
835 * get_track_name might fail */
836 *(track_names
+ string_index
+ fn_idx
-1) = '\0';
839 else /* request more buffer so that track and filename fit */
840 len
= (avail
- remain
) + MAX_PATH
;
845 if (!free_slide_prio(0))
848 buflib_buffer_out(&buf_ctx
, &out
);
853 struct track_data
*new_tracks
= (struct track_data
*)(out
+ (uintptr_t)tracks
);
854 unsigned int bytes
= track_count
* sizeof(struct track_data
);
855 rb
->memmove(new_tracks
, tracks
, bytes
);
864 tracks
->sort
= ((disc_num
- 1) << 24) + (track_num
<< 14) + track_count
;
865 tracks
->name_idx
= string_index
;
866 tracks
->seek
= tcs
.result_seek
;
867 tracks
->filename_idx
= fn_idx
+ string_index
;
872 rb
->tagcache_search_finish(&tcs
);
874 /* now fix the track list order */
875 rb
->qsort(tracks
, track_count
, sizeof(struct track_data
), compare_tracks
);
883 Determine filename of the album art for the given slide_index and
884 store the result in buf.
885 The algorithm looks for the first track of the given album uses
886 find_albumart to find the filename.
888 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
891 if ( slide_index
== -1 )
893 rb
->strncpy( buf
, EMPTY_SLIDE
, buflen
);
896 if (!rb
->tagcache_search(&tcs
, tag_filename
))
900 /* find the first track of the album */
901 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
903 if ( rb
->tagcache_get_next(&tcs
) ) {
907 #ifdef HAVE_TC_RAMCACHE
908 if (rb
->tagcache_fill_tags(&id3
, tcs
.result
))
910 rb
->strncpy(id3
.path
, tcs
.result
, sizeof(id3
.path
));
911 id3
.path
[sizeof(id3
.path
) - 1] = 0;
916 fd
= rb
->open(tcs
.result
, O_RDONLY
);
917 rb
->get_metadata(&id3
, fd
, tcs
.result
);
920 if ( search_albumart_files(&id3
, ":", buf
, buflen
) )
926 /* did not find a matching track */
929 rb
->tagcache_search_finish(&tcs
);
934 Draw the PictureFlow logo
936 void draw_splashscreen(void)
938 unsigned char * buf_tmp
= buf
;
939 size_t buf_tmp_size
= buf_size
;
940 struct screen
* display
= rb
->screens
[0];
942 ALIGN_BUFFER(buf_tmp
, buf_tmp_size
, sizeof(fb_data
));
944 struct bitmap logo
= {
954 int ret
= rb
->read_bmp_file(SPLASH_BMP
, &logo
, buf_tmp_size
, FORMAT_NATIVE
,
957 rb
->lcd_set_background(N_BRIGHT(0));
958 rb
->lcd_set_foreground(N_BRIGHT(255));
960 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
962 rb
->lcd_clear_display();
966 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
967 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
969 display
->bitmap(logo
.data
, (LCD_WIDTH
- logo
.width
) / 2, 10,
970 logo
.width
, logo
.height
);
971 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
972 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
981 Draw a simple progress bar
983 void draw_progressbar(int step
)
986 const int bar_height
= 22;
987 const int w
= LCD_WIDTH
- 20;
990 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
992 int y
= (LCD_HEIGHT
- txt_h
)/2;
994 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
998 rb
->lcd_set_foreground(N_BRIGHT(100));
1000 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
1002 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
1005 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
1007 rb
->lcd_set_foreground(N_BRIGHT(255));
1014 Precomupte the album art images and store them in CACHE_PREFIX.
1016 bool create_albumart_cache(void)
1021 struct bitmap input_bmp
;
1023 char pfraw_file
[MAX_PATH
];
1024 char albumart_file
[MAX_PATH
];
1025 unsigned int format
= FORMAT_NATIVE
;
1027 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
1029 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
1030 for (i
=0; i
< album_count
; i
++)
1032 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%d.pfraw",
1034 /* delete existing cache, so it's a true rebuild */
1035 if(rb
->file_exists(pfraw_file
))
1036 rb
->remove(pfraw_file
);
1037 draw_progressbar(i
);
1038 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
1041 input_bmp
.data
= buf
;
1042 input_bmp
.width
= DISPLAY_WIDTH
;
1043 input_bmp
.height
= DISPLAY_HEIGHT
;
1044 ret
= read_image_file(albumart_file
, &input_bmp
,
1045 buf_size
, format
, &format_transposed
);
1047 rb
->splash(HZ
, "Could not read bmp");
1048 continue; /* skip missing/broken files */
1050 if (!save_pfraw(pfraw_file
, &input_bmp
))
1052 rb
->splash(HZ
, "Could not write bmp");
1055 if ( rb
->button_get(false) == PF_MENU
) return false;
1057 if ( slides
== 0 ) {
1058 /* Warn the user that we couldn't find any albumart */
1059 rb
->splash(2*HZ
, "No album art found");
1066 Thread used for loading and preparing bitmaps in the background
1070 long sleep_time
= 5 * HZ
;
1071 struct queue_event ev
;
1073 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1078 /* we just woke up */
1081 while ( load_new_slide() ) {
1093 End the thread by posting the EV_EXIT event
1095 void end_pf_thread(void)
1097 if ( thread_is_running
) {
1098 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
1099 rb
->thread_wait(thread_id
);
1100 /* remove the thread's queue from the broadcast list */
1101 rb
->queue_delete(&thread_q
);
1102 thread_is_running
= false;
1109 Create the thread an setup the event queue
1111 bool create_pf_thread(void)
1113 /* put the thread's queue in the bcast list */
1114 rb
->queue_init(&thread_q
, true);
1115 if ((thread_id
= rb
->create_thread(
1118 sizeof(thread_stack
),
1120 "Picture load thread"
1121 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1122 PRIORITY_REALTIME
+ 1))
1128 thread_is_running
= true;
1129 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1134 Safe the given bitmap as filename in the pfraw format
1136 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1138 struct pfraw_header bmph
;
1139 bmph
.width
= bm
->width
;
1140 bmph
.height
= bm
->height
;
1141 int fh
= rb
->creat( filename
);
1142 if( fh
< 0 ) return false;
1143 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1145 for( y
= 0; y
< bm
->height
; y
++ )
1147 pix_t
*d
= (pix_t
*)( bm
->data
) + (y
*bm
->width
);
1148 rb
->write( fh
, d
, sizeof( pix_t
) * bm
->width
);
1156 * The following functions implement the linked-list-in-array used to manage
1157 * the LRU cache of slides, and the list of free cache slots.
1160 #define seek_right_while(start, cond) \
1162 int ind_, next_ = (start); \
1165 next_ = cache[ind_].next; \
1166 } while (next_ != cache_used && (cond)); \
1170 #define seek_left_while(start, cond) \
1172 int ind_, next_ = (start); \
1175 next_ = cache[ind_].prev; \
1176 } while (ind_ != cache_used && (cond)); \
1181 Pop the given item from the linked list starting at *head, returning the next
1182 item, or -1 if the list is now empty.
1184 static inline int lla_pop_item (int *head
, int i
)
1186 int prev
= cache
[i
].prev
;
1187 int next
= cache
[i
].next
;
1193 else if (i
== *head
)
1195 cache
[next
].prev
= prev
;
1196 cache
[prev
].next
= next
;
1202 Pop the head item from the list starting at *head, returning the index of the
1203 item, or -1 if the list is already empty.
1205 static inline int lla_pop_head (int *head
)
1209 lla_pop_item(head
, i
);
1214 Insert the item at index i before the one at index p.
1216 static inline void lla_insert (int i
, int p
)
1219 int prev
= cache
[next
].prev
;
1220 cache
[next
].prev
= i
;
1221 cache
[prev
].next
= i
;
1222 cache
[i
].next
= next
;
1223 cache
[i
].prev
= prev
;
1228 Insert the item at index i at the end of the list starting at *head.
1230 static inline void lla_insert_tail (int *head
, int i
)
1238 lla_insert(i
, *head
);
1242 Insert the item at index i before the one at index p.
1244 static inline void lla_insert_after(int i
, int p
)
1252 Insert the item at index i before the one at index p in the list starting at
1255 static inline void lla_insert_before(int *head
, int i
, int p
)
1264 Free the used slide at index i, and its buffer, and move it to the free
1267 static inline void free_slide(int i
)
1269 if (cache
[i
].hid
!= empty_slide_hid
)
1270 buflib_free(&buf_ctx
, cache
[i
].hid
);
1271 cache
[i
].index
= -1;
1272 lla_pop_item(&cache_used
, i
);
1273 lla_insert_tail(&cache_free
, i
);
1274 if (cache_used
== -1)
1276 cache_right_index
= -1;
1277 cache_left_index
= -1;
1278 cache_center_index
= -1;
1284 Free one slide ranked above the given priority. If no such slide can be found,
1287 static bool free_slide_prio(int prio
)
1289 if (cache_used
== -1)
1291 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1292 int prio_l
= cache
[l
].index
< center_index
?
1293 center_index
- cache
[l
].index
: 0;
1294 int prio_r
= cache
[r
].index
> center_index
?
1295 cache
[r
].index
- center_index
: 0;
1296 if (prio_l
> prio_r
)
1304 if (prio_max
> prio
)
1306 if (i
== cache_left_index
)
1307 cache_left_index
= cache
[i
].next
;
1308 if (i
== cache_right_index
)
1309 cache_right_index
= cache
[i
].prev
;
1317 Read the pfraw image given as filename and return the hid of the buffer
1319 int read_pfraw(char* filename
, int prio
)
1321 struct pfraw_header bmph
;
1322 int fh
= rb
->open(filename
, O_RDONLY
);
1324 return empty_slide_hid
;
1326 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1328 int size
= sizeof(struct bitmap
) + sizeof( pix_t
) *
1329 bmph
.width
* bmph
.height
;
1332 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1339 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1341 bm
->width
= bmph
.width
;
1342 bm
->height
= bmph
.height
;
1343 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1346 for( y
= 0; y
< bm
->height
; y
++ )
1348 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1357 Load the surface for the given slide_index into the cache at cache_index.
1359 static inline bool load_and_prepare_surface(const int slide_index
,
1360 const int cache_index
,
1363 char tmp_path_name
[MAX_PATH
+1];
1364 rb
->snprintf(tmp_path_name
, sizeof(tmp_path_name
), CACHE_PREFIX
"/%d.pfraw",
1367 int hid
= read_pfraw(tmp_path_name
, prio
);
1371 cache
[cache_index
].hid
= hid
;
1373 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1374 cache
[cache_index
].index
= slide_index
;
1382 Load the "next" slide that we can load, freeing old slides if needed, provided
1383 that they are further from center_index than the current slide
1385 bool load_new_slide(void)
1388 if (cache_center_index
!= -1)
1391 if (cache
[cache_center_index
].index
!= center_index
)
1393 if (cache
[cache_center_index
].index
< center_index
)
1395 cache_center_index
= seek_right_while(cache_center_index
,
1396 cache
[next_
].index
<= center_index
);
1397 prev
= cache_center_index
;
1398 next
= cache
[cache_center_index
].next
;
1402 cache_center_index
= seek_left_while(cache_center_index
,
1403 cache
[next_
].index
>= center_index
);
1404 next
= cache_center_index
;
1405 prev
= cache
[cache_center_index
].prev
;
1407 if (cache
[cache_center_index
].index
!= center_index
)
1409 if (cache_free
== -1)
1411 i
= lla_pop_head(&cache_free
);
1412 if (!load_and_prepare_surface(center_index
, i
, 0))
1413 goto fail_and_refree
;
1414 if (cache
[next
].index
== -1)
1416 if (cache
[prev
].index
== -1)
1417 goto insert_first_slide
;
1419 next
= cache
[prev
].next
;
1421 lla_insert(i
, next
);
1422 if (cache
[i
].index
< cache
[cache_used
].index
)
1424 cache_center_index
= i
;
1425 cache_left_index
= i
;
1426 cache_right_index
= i
;
1430 if (cache
[cache_left_index
].index
>
1431 cache
[cache_center_index
].index
)
1432 cache_left_index
= cache_center_index
;
1433 if (cache
[cache_right_index
].index
<
1434 cache
[cache_center_index
].index
)
1435 cache_right_index
= cache_center_index
;
1436 cache_left_index
= seek_left_while(cache_left_index
,
1437 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1438 cache_right_index
= seek_right_while(cache_right_index
,
1439 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1440 int prio_l
= cache
[cache_center_index
].index
-
1441 cache
[cache_left_index
].index
+ 1;
1442 int prio_r
= cache
[cache_right_index
].index
-
1443 cache
[cache_center_index
].index
+ 1;
1444 if ((prio_l
< prio_r
||
1445 cache
[cache_right_index
].index
>= number_of_slides
) &&
1446 cache
[cache_left_index
].index
> 0)
1448 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1450 i
= lla_pop_head(&cache_free
);
1451 if (load_and_prepare_surface(cache
[cache_left_index
].index
1454 lla_insert_before(&cache_used
, i
, cache_left_index
);
1455 cache_left_index
= i
;
1458 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1460 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1462 i
= lla_pop_head(&cache_free
);
1463 if (load_and_prepare_surface(cache
[cache_right_index
].index
1466 lla_insert_after(i
, cache_right_index
);
1467 cache_right_index
= i
;
1472 i
= lla_pop_head(&cache_free
);
1473 if (load_and_prepare_surface(center_index
, i
, 0))
1478 cache_center_index
= i
;
1479 cache_left_index
= i
;
1480 cache_right_index
= i
;
1488 lla_insert_tail(&cache_free
, i
);
1495 Get a slide from the buffer
1497 static inline struct dim
*get_slide(const int hid
)
1504 bmp
= buflib_get_data(&buf_ctx
, hid
);
1511 Return the requested surface
1513 static inline struct dim
*surface(const int slide_index
)
1515 if (slide_index
< 0)
1517 if (slide_index
>= number_of_slides
)
1520 if ((i
= cache_used
) != -1)
1523 if (cache
[i
].index
== slide_index
)
1524 return get_slide(cache
[i
].hid
);
1526 } while (i
!= cache_used
);
1528 return get_slide(empty_slide_hid
);
1532 adjust slides so that they are in "steady state" position
1534 void reset_slides(void)
1536 center_slide
.angle
= 0;
1537 center_slide
.cx
= 0;
1538 center_slide
.cy
= 0;
1539 center_slide
.distance
= 0;
1540 center_slide
.slide_index
= center_index
;
1543 for (i
= 0; i
< num_slides
; i
++) {
1544 struct slide_data
*si
= &left_slides
[i
];
1546 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1548 si
->slide_index
= center_index
- 1 - i
;
1552 for (i
= 0; i
< num_slides
; i
++) {
1553 struct slide_data
*si
= &right_slides
[i
];
1555 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1557 si
->slide_index
= center_index
+ 1 + i
;
1564 Updates look-up table and other stuff necessary for the rendering.
1565 Call this when the viewport size or slide dimension is changed.
1567 * To calculate the offset that will provide the proper margin, we use the same
1568 * projection used to render the slides. The solution for xc, the slide center,
1570 * xp * (zo + xs * sin(r))
1571 * xc = xp - xs * cos(r) + ───────────────────────
1573 * TODO: support moving the side slides toward or away from the camera
1575 void recalc_offsets(void)
1577 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1579 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1580 PFREAL_ONE
) * zoom
/ 100;
1583 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1584 cosr
= fcos(-itilt
);
1585 sinr
= fsin(-itilt
);
1586 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1587 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1588 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1589 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1591 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1596 Fade the given color by spreading the fb_data (ushort)
1597 to an uint, multiply and compress the result back to a ushort.
1599 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1600 static inline unsigned fade_color(pix_t c
, unsigned a
)
1602 unsigned int result
;
1604 a
= (a
+ 2) & 0x1fc;
1605 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1606 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1608 return swap16(result
);
1610 #elif LCD_PIXELFORMAT == RGB565
1611 static inline unsigned fade_color(pix_t c
, unsigned a
)
1613 unsigned int result
;
1614 a
= (a
+ 2) & 0x1fc;
1615 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1616 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1621 static inline unsigned fade_color(pix_t c
, unsigned a
)
1624 return MULUQ(val
, a
) >> 8;
1629 * Render a single slide
1630 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1631 * on the slide from its center, zo is the slide's depth offset from the plane
1632 * of the display, r is the angle at which the slide is tilted, and xp is the
1633 * point on the display corresponding to xs on the slide, the projection
1636 * z * (xc + xs * cos(r))
1637 * xp = ──────────────────────
1638 * z + zo + xs * sin(r)
1640 * z * (xc - xp) - xp * zo
1641 * xs = ────────────────────────
1642 * xp * sin(r) - z * cos(r)
1644 * We use the xp projection once, to find the left edge of the slide on the
1645 * display. From there, we use the xs reverse projection to find the horizontal
1646 * offset from the slide center of each column on the screen, until we reach
1647 * the right edge of the slide, or the screen. The reverse projection can be
1648 * optimized by saving the numerator and denominator of the fraction, which can
1649 * then be incremented by (z + zo) and sin(r) respectively.
1651 void render_slide(struct slide_data
*slide
, const int alpha
)
1653 struct dim
*bmp
= surface(slide
->slide_index
);
1657 if (slide
->angle
> 255 || slide
->angle
< -255)
1659 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1661 const int sw
= bmp
->width
;
1662 const int sh
= bmp
->height
;
1663 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1664 const int w
= LCD_WIDTH
;
1666 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1668 if (alpha
== 256) { /* opaque -> copy table */
1669 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1670 } else { /* precalculate faded table */
1672 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1673 lalpha
= reflect_table
[i
];
1674 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1678 PFreal cosr
= fcos(slide
->angle
);
1679 PFreal sinr
= fsin(slide
->angle
);
1680 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1681 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1682 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1683 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1684 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1686 /* Since we're finding the screen position of the left edge of the slide,
1689 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1691 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1695 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1696 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1697 xs
= fdiv(xsnum
, xsden
);
1699 xsnumi
= -CAM_DIST_R
- zo
;
1702 int dy
= PFREAL_ONE
;
1703 for (x
= xi
; x
< w
; x
++) {
1704 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1707 if (zo
|| slide
->angle
)
1708 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1710 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1711 const int pixelstep
= BUFFER_WIDTH
;
1713 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1714 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1715 pix_t
*pixel
= &buffer
[((LCD_HEIGHT
/2)-1)*BUFFER_WIDTH
+ x
];
1719 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1725 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1730 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1731 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1732 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1733 p
+ (LCD_HEIGHT
/2) * dy
);
1734 pixel
= &buffer
[(LCD_HEIGHT
/2)*BUFFER_WIDTH
+ x
];
1738 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1744 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1750 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1751 int lalpha
= reftab
[ty
];
1752 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1757 if (zo
|| slide
->angle
)
1761 xs
= fdiv(xsnum
, xsden
);
1766 /* let the music play... */
1773 Jump the the given slide_index
1775 static inline void set_current_slide(const int slide_index
)
1777 int old_center_index
= center_index
;
1779 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1780 if (old_center_index
!= center_index
)
1781 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1782 target
= center_index
;
1783 slide_frame
= slide_index
<< 16;
1788 Start the animation for changing slides
1790 void start_animation(void)
1792 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1793 pf_state
= pf_scrolling
;
1797 Go to the previous slide
1799 void show_previous_slide(void)
1802 if (center_index
> 0) {
1803 target
= center_index
- 1;
1806 } else if ( step
> 0 ) {
1807 target
= center_index
;
1810 target
= fmax(0, center_index
- 2);
1816 Go to the next slide
1818 void show_next_slide(void)
1821 if (center_index
< number_of_slides
- 1) {
1822 target
= center_index
+ 1;
1825 } else if ( step
< 0 ) {
1826 target
= center_index
;
1829 target
= fmin(center_index
+ 2, number_of_slides
- 1);
1835 Render the slides. Updates only the offscreen buffer.
1837 void render_all_slides(void)
1839 MYLCD(set_background
)(G_BRIGHT(0));
1840 /* TODO: Optimizes this by e.g. invalidating rects */
1841 MYLCD(clear_display
)();
1843 int nleft
= num_slides
;
1844 int nright
= num_slides
;
1848 /* no animation, boring plain rendering */
1849 for (index
= nleft
- 2; index
>= 0; index
--) {
1850 int alpha
= (index
< nleft
- 2) ? 256 : 128;
1851 alpha
-= extra_fade
;
1853 render_slide(&left_slides
[index
], alpha
);
1855 for (index
= nright
- 2; index
>= 0; index
--) {
1856 int alpha
= (index
< nright
- 2) ? 256 : 128;
1857 alpha
-= extra_fade
;
1859 render_slide(&right_slides
[index
], alpha
);
1862 /* the first and last slide must fade in/fade out */
1863 for (index
= nleft
- 1; index
>= 0; index
--) {
1865 if (index
== nleft
- 1)
1866 alpha
= (step
> 0) ? 0 : 128 - fade
/ 2;
1867 if (index
== nleft
- 2)
1868 alpha
= (step
> 0) ? 128 - fade
/ 2 : 256 - fade
/ 2;
1869 if (index
== nleft
- 3)
1870 alpha
= (step
> 0) ? 256 - fade
/ 2 : 256;
1871 render_slide(&left_slides
[index
], alpha
);
1873 for (index
= nright
- 1; index
>= 0; index
--) {
1874 int alpha
= (index
< nright
- 2) ? 256 : 128;
1875 if (index
== nright
- 1)
1876 alpha
= (step
> 0) ? fade
/ 2 : 0;
1877 if (index
== nright
- 2)
1878 alpha
= (step
> 0) ? 128 + fade
/ 2 : fade
/ 2;
1879 if (index
== nright
- 3)
1880 alpha
= (step
> 0) ? 256 : 128 + fade
/ 2;
1881 render_slide(&right_slides
[index
], alpha
);
1884 render_slide(¢er_slide
, 256);
1889 Updates the animation effect. Call this periodically from a timer.
1891 void update_scroll_animation(void)
1899 /* deaccelerate when approaching the target */
1901 const int max
= 2 * 65536;
1903 int fi
= slide_frame
;
1904 fi
-= (target
<< 16);
1909 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
1910 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
1913 slide_frame
+= speed
* step
;
1915 int index
= slide_frame
>> 16;
1916 int pos
= slide_frame
& 0xffff;
1917 int neg
= 65536 - pos
;
1918 int tick
= (step
< 0) ? neg
: pos
;
1919 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
1921 /* the leftmost and rightmost slide must fade away */
1926 if (center_index
!= index
) {
1927 center_index
= index
;
1928 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1929 slide_frame
= index
<< 16;
1930 center_slide
.slide_index
= center_index
;
1931 for (i
= 0; i
< num_slides
; i
++)
1932 left_slides
[i
].slide_index
= center_index
- 1 - i
;
1933 for (i
= 0; i
< num_slides
; i
++)
1934 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
1937 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
1938 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
1939 center_slide
.cy
= fmul(offsetY
, ftick
);
1941 if (center_index
== target
) {
1949 for (i
= 0; i
< num_slides
; i
++) {
1950 struct slide_data
*si
= &left_slides
[i
];
1953 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
1954 * slide_spacing
* ftick
);
1958 for (i
= 0; i
< num_slides
; i
++) {
1959 struct slide_data
*si
= &right_slides
[i
];
1962 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
1963 * slide_spacing
* ftick
;
1968 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
1969 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
1970 right_slides
[0].cx
= fmul(offsetX
, ftick
);
1971 right_slides
[0].cy
= fmul(offsetY
, ftick
);
1973 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
1974 left_slides
[0].angle
= (pos
* itilt
) >> 16;
1975 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
1976 left_slides
[0].cy
= fmul(offsetY
, ftick
);
1979 /* must change direction ? */
1992 void cleanup(void *parameter
)
1995 /* Turn on backlight timeout (revert to settings) */
1996 backlight_use_settings(); /* backlight control in lib/helper.c */
2004 Create the "?" slide, that is shown while loading
2005 or when no cover was found.
2007 int create_empty_slide(bool force
)
2009 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
2010 struct bitmap input_bmp
;
2012 input_bmp
.width
= DISPLAY_WIDTH
;
2013 input_bmp
.height
= DISPLAY_HEIGHT
;
2015 input_bmp
.format
= FORMAT_NATIVE
;
2017 input_bmp
.data
= (char*)buf
;
2018 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
2020 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
2021 &format_transposed
);
2022 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
2030 Shows the album name setting menu
2032 int album_name_menu(void)
2034 int selection
= show_album_name
;
2036 MENUITEM_STRINGLIST(album_name_menu
,"Show album title",NULL
,
2037 "Hide album title", "Show at the bottom", "Show at the top");
2038 rb
->do_menu(&album_name_menu
, &selection
, NULL
, false);
2040 show_album_name
= selection
;
2041 return GO_TO_PREVIOUS
;
2045 Shows the settings menu
2047 int settings_menu(void)
2052 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
2053 "Spacing", "Centre margin", "Number of slides", "Zoom",
2054 "Show album title", "Resize Covers", "Rebuild cache");
2057 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2060 rb
->set_bool("Show FPS", &show_fps
);
2065 rb
->set_int("Spacing between slides", "", 1,
2067 NULL
, 1, 0, 100, NULL
);
2073 rb
->set_int("Centre margin", "", 1,
2075 NULL
, 1, 0, 80, NULL
);
2081 rb
->set_int("Number of slides", "", 1, &num_slides
,
2082 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2088 rb
->set_int("Zoom", "", 1, &zoom
,
2089 NULL
, 1, 10, 300, NULL
);
2101 rb
->set_bool("Resize Covers", &resize
);
2102 if (old_val
== resize
) /* changed? */
2104 /* fallthrough if changed, since cache needs to be rebuilt */
2107 rb
->remove(EMPTY_SLIDE
);
2108 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2111 case MENU_ATTACHED_USB
:
2112 return PLUGIN_USB_CONNECTED
;
2114 } while ( selection
>= 0 );
2115 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2128 rb
->lcd_set_foreground(N_BRIGHT(255));
2131 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2132 "Settings", "Return", "Quit");
2134 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2136 result
= settings_menu();
2137 if ( result
!= 0 ) return result
;
2146 case MENU_ATTACHED_USB
:
2147 return PLUGIN_USB_CONNECTED
;
2156 Animation step for zooming into the current cover
2158 void update_cover_in_animation(void)
2160 cover_animation_keyframe
++;
2161 if( cover_animation_keyframe
< 20 ) {
2162 center_slide
.distance
-=5;
2163 center_slide
.angle
+=1;
2166 else if( cover_animation_keyframe
< 35 ) {
2167 center_slide
.angle
+=16;
2170 cover_animation_keyframe
= 0;
2171 pf_state
= pf_show_tracks
;
2176 Animation step for zooming out the current cover
2178 void update_cover_out_animation(void)
2180 cover_animation_keyframe
++;
2181 if( cover_animation_keyframe
<= 15 ) {
2182 center_slide
.angle
-=16;
2184 else if( cover_animation_keyframe
< 35 ) {
2185 center_slide
.distance
+=5;
2186 center_slide
.angle
-=1;
2190 cover_animation_keyframe
= 0;
2196 Draw a blue gradient at y with height h
2198 static inline void draw_gradient(int y
, int h
)
2200 static int r
, inc
, c
;
2201 inc
= (100 << 8) / h
;
2203 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2204 int c2
= selected_track_pulse
- 5;
2205 for (r
=0; r
<h
; r
++) {
2206 #ifdef HAVE_LCD_COLOR
2207 MYLCD(set_foreground
)(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2210 MYLCD(set_foreground
)(G_BRIGHT(c2
+160-(c
>> 8)));
2212 MYLCD(hline
)(0, LCD_WIDTH
, r
+y
);
2221 static void track_list_yh(int char_height
)
2223 switch (show_album_name
)
2225 case album_name_hide
:
2226 track_list_y
= (show_fps
? char_height
: 0);
2227 track_list_h
= LCD_HEIGHT
- track_list_y
;
2229 case album_name_bottom
:
2230 track_list_y
= (show_fps
? char_height
: 0);
2231 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2233 default: /* case album_name_top */
2234 track_list_y
= char_height
* 2;
2235 track_list_h
= LCD_HEIGHT
- track_list_y
-
2236 (show_fps
? char_height
: 0);
2242 Reset the track list after a album change
2244 void reset_track_list(void)
2246 int albumtxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2247 track_list_yh(albumtxt_h
);
2248 track_list_visible_entries
= fmin( track_list_h
/albumtxt_h
, track_count
);
2249 start_index_track_list
= 0;
2250 track_scroll_index
= 0;
2251 track_scroll_dir
= 1;
2254 /* let the tracklist start more centered
2255 * if the screen isn't filled with tracks */
2256 if (track_count
*albumtxt_h
< track_list_h
)
2258 track_list_h
= track_count
* albumtxt_h
;
2259 track_list_y
= LCD_HEIGHT
/ 2 - (track_list_h
/ 2);
2264 Display the list of tracks
2266 void show_track_list(void)
2268 MYLCD(clear_display
)();
2269 if ( center_slide
.slide_index
!= track_index
) {
2270 create_track_index(center_slide
.slide_index
);
2273 static int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2274 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2276 int titletxt_y
= track_list_y
;
2278 track_i
= start_index_track_list
;
2279 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2282 MYLCD(getstringsize
)(get_track_name(track_i
), &titletxt_w
, NULL
);
2283 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2284 if ( track_i
== selected_track
) {
2285 draw_gradient(titletxt_y
, titletxt_h
);
2286 MYLCD(set_foreground
)(G_BRIGHT(255));
2287 if (titletxt_w
> LCD_WIDTH
) {
2288 if ( titletxt_w
+ track_scroll_index
<= LCD_WIDTH
)
2289 track_scroll_dir
= 1;
2290 else if ( track_scroll_index
>= 0 ) track_scroll_dir
= -1;
2291 track_scroll_index
+= track_scroll_dir
*2;
2292 titletxt_x
= track_scroll_index
;
2294 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2297 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2298 MYLCD(set_foreground
)(G_BRIGHT(color
));
2299 MYLCD(putsxy
)(titletxt_x
,titletxt_y
,get_track_name(track_i
));
2301 titletxt_y
+= titletxt_h
;
2305 void select_next_track(void)
2307 if ( selected_track
< track_count
- 1 ) {
2309 track_scroll_index
= 0;
2310 track_scroll_dir
= 1;
2311 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2312 start_index_track_list
++;
2316 void select_prev_track(void)
2318 if (selected_track
> 0 ) {
2319 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2320 track_scroll_index
= 0;
2321 track_scroll_dir
= 1;
2327 * Puts the current tracklist into a newly created playlist and starts playling
2329 void start_playback(void)
2331 static int old_playlist
= -1;
2332 if (center_slide
.slide_index
== old_playlist
)
2334 rb
->playlist_start(selected_track
, 0);
2336 /* First, replace the current playlist with a new one */
2337 else if ((rb
->playlist_remove_all_tracks(NULL
) == 0) &&
2338 (rb
->playlist_create(PLUGIN_DEMOS_DIR
, NULL
) == 0))
2342 if (rb
->playlist_add(get_track_filename(count
)) != 0)
2344 } while(++count
< track_count
);
2346 rb
->playlist_sync(NULL
);
2348 rb
->playlist_start(selected_track
, 0);
2350 old_playlist
= center_slide
.slide_index
;
2355 Draw the current album name
2357 void draw_album_text(void)
2359 if (0 == show_album_name
)
2362 int albumtxt_w
, albumtxt_h
;
2367 /* Draw album text */
2368 if ( pf_state
== pf_scrolling
) {
2369 c
= ((slide_frame
& 0xffff )/ 255);
2370 if (step
< 0) c
= 255-c
;
2371 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2372 albumtxt
= get_album_name(center_index
+step
);
2376 albumtxt
= get_album_name(center_index
);
2382 albumtxt
= get_album_name(center_index
);
2385 MYLCD(set_foreground
)(G_BRIGHT(c
));
2386 MYLCD(getstringsize
)(albumtxt
, &albumtxt_w
, &albumtxt_h
);
2387 if (center_index
!= prev_center_index
) {
2390 prev_center_index
= center_index
;
2393 if (show_album_name
== album_name_top
)
2394 albumtxt_y
= albumtxt_h
/ 2;
2396 albumtxt_y
= LCD_HEIGHT
- albumtxt_h
- albumtxt_h
/2;
2398 if (albumtxt_w
> LCD_WIDTH
) {
2399 MYLCD(putsxy
)(albumtxt_x
, albumtxt_y
, albumtxt
);
2400 if ( pf_state
== pf_idle
|| pf_state
== pf_show_tracks
) {
2401 if ( albumtxt_w
+ albumtxt_x
<= LCD_WIDTH
) albumtxt_dir
= 1;
2402 else if ( albumtxt_x
>= 0 ) albumtxt_dir
= -1;
2403 albumtxt_x
+= albumtxt_dir
;
2407 MYLCD(putsxy
)((LCD_WIDTH
- albumtxt_w
) /2, albumtxt_y
, albumtxt
);
2414 Display an error message and wait for input.
2416 void error_wait(const char *message
)
2418 rb
->splashf(0, "%s. Press any button to continue.", message
);
2419 while (rb
->get_action(CONTEXT_STD
, 1) == ACTION_NONE
)
2425 Main function that also contain the main plasma
2432 rb
->lcd_setfont(FONT_UI
);
2433 draw_splashscreen();
2435 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2436 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2437 error_wait("Could not create directory " CACHE_PREFIX
);
2438 return PLUGIN_ERROR
;
2442 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2444 init_reflect_table();
2446 ALIGN_BUFFER(buf
, buf_size
, 4);
2447 ret
= create_album_index();
2448 if (ret
== ERROR_BUFFER_FULL
) {
2449 error_wait("Not enough memory for album names");
2450 return PLUGIN_ERROR
;
2451 } else if (ret
== ERROR_NO_ALBUMS
) {
2452 error_wait("No albums found. Please enable database");
2453 return PLUGIN_ERROR
;
2456 ALIGN_BUFFER(buf
, buf_size
, 4);
2457 number_of_slides
= album_count
;
2458 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2459 error_wait("Could not create album art cache");
2460 return PLUGIN_ERROR
;
2463 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2464 error_wait("Could not load the empty slide");
2465 return PLUGIN_ERROR
;
2467 cache_version
= CACHE_VERSION
;
2468 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2473 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2474 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2476 error_wait("Greylib init failed!");
2477 return PLUGIN_ERROR
;
2479 grey_setfont(FONT_UI
);
2480 buf_size
-= grey_buf_used
;
2481 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2483 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2485 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2487 error_wait("Unable to load empty slide image");
2488 return PLUGIN_ERROR
;
2491 if (!create_pf_thread()) {
2492 error_wait("Cannot create thread!");
2493 return PLUGIN_ERROR
;
2499 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2502 cache
[i
].next
= i
+ 1;
2503 cache
[i
].prev
= i
- 1;
2505 cache
[0].prev
= i
- 1;
2506 cache
[i
- 1].next
= 0;
2526 long last_update
= *rb
->current_tick
;
2527 long current_update
;
2528 long update_interval
= 100;
2532 bool instant_update
;
2535 grey_set_drawmode(DRMODE_FG
);
2537 rb
->lcd_set_drawmode(DRMODE_FG
);
2539 current_update
= *rb
->current_tick
;
2542 /* Initial rendering */
2543 instant_update
= false;
2546 switch ( pf_state
) {
2548 update_scroll_animation();
2549 render_all_slides();
2550 instant_update
= true;
2553 update_cover_in_animation();
2554 render_all_slides();
2555 instant_update
= true;
2558 update_cover_out_animation();
2559 render_all_slides();
2560 instant_update
= true;
2562 case pf_show_tracks
:
2566 render_all_slides();
2571 if (current_update
- last_update
> update_interval
) {
2572 fps
= frames
* HZ
/ (current_update
- last_update
);
2573 last_update
= current_update
;
2580 MYLCD(set_foreground
)(G_BRIGHT(255));
2582 MYLCD(set_foreground
)(G_PIX(255,0,0));
2584 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2585 if (show_album_name
== album_name_top
)
2586 fpstxt_y
= LCD_HEIGHT
-
2587 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2590 MYLCD(putsxy
)(0, fpstxt_y
, fpstxt
);
2595 /* Copy offscreen buffer to LCD and give time to other threads */
2599 /*/ Handle buttons */
2600 button
= rb
->get_custom_action(CONTEXT_CUSTOM
|
2601 (pf_state
== pf_show_tracks
? 1 : 0),
2602 instant_update
? 0 : HZ
/16,
2610 if ( pf_state
== pf_show_tracks
)
2612 buflib_buffer_in(&buf_ctx
, borrowed
);
2615 pf_state
= pf_cover_out
;
2617 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2626 if ( ret
== -1 ) return PLUGIN_OK
;
2627 if ( ret
!= 0 ) return ret
;
2631 MYLCD(set_drawmode
)(DRMODE_FG
);
2635 case PF_NEXT_REPEAT
:
2636 if ( pf_state
== pf_show_tracks
)
2637 select_next_track();
2638 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2643 case PF_PREV_REPEAT
:
2644 if ( pf_state
== pf_show_tracks
)
2645 select_prev_track();
2646 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2647 show_previous_slide();
2651 if ( pf_state
== pf_idle
) {
2652 pf_state
= pf_cover_in
;
2654 else if ( pf_state
== pf_show_tracks
) {
2660 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
2661 == SYS_USB_CONNECTED
)
2662 return PLUGIN_USB_CONNECTED
;
2670 /*************************** Plugin entry point ****************************/
2672 enum plugin_status
plugin_start(const void *parameter
)
2677 rb
->lcd_set_backdrop(NULL
);
2679 /* Turn off backlight timeout */
2680 backlight_force_on(); /* backlight control in lib/helper.c */
2681 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2682 rb
->cpu_boost(true);
2684 #if PLUGIN_BUFFER_SIZE > 0x10000
2685 buf
= rb
->plugin_get_buffer(&buf_size
);
2687 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2690 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2691 rb
->cpu_boost(false);
2693 if ( ret
== PLUGIN_OK
) {
2694 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2697 rb
->splash(HZ
, "Error writing config.");