summaryrefslogtreecommitdiff
path: root/apps/plugins/pictureflow/pictureflow.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/pictureflow/pictureflow.c')
-rw-r--r--apps/plugins/pictureflow/pictureflow.c2575
1 files changed, 2575 insertions, 0 deletions
diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c
new file mode 100644
index 0000000000..82dc9748ec
--- /dev/null
+++ b/apps/plugins/pictureflow/pictureflow.c
@@ -0,0 +1,2575 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8* $Id$
9*
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)
13*
14* Original code: http://code.google.com/p/pictureflow/
15*
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.
20*
21* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22* KIND, either express or implied.
23*
24****************************************************************************/
25
26#include "plugin.h"
27#include <albumart.h>
28#include "lib/read_image.h"
29#include "lib/pluginlib_actions.h"
30#include "lib/helper.h"
31#include "lib/configfile.h"
32#include "lib/picture.h"
33#include "pluginbitmaps/pictureflow_logo.h"
34#include "lib/grey.h"
35#include "lib/feature_wrappers.h"
36#include "lib/buflib.h"
37
38PLUGIN_HEADER
39
40/******************************* Globals ***********************************/
41
42#define PF_PREV ACTION_STD_PREV
43#define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
44#define PF_NEXT ACTION_STD_NEXT
45#define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
46#define PF_SELECT ACTION_STD_OK
47#define PF_CONTEXT ACTION_STD_CONTEXT
48#define PF_BACK ACTION_STD_CANCEL
49#define PF_MENU ACTION_STD_MENU
50#define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
51
52const struct button_mapping pf_context_album_scroll[] =
53{
54#ifdef HAVE_TOUCHSCREEN
55 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
56 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
57 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
58 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
59#endif
60#if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
61 CONFIG_KEYPAD == IAUDIO_X5M5_PAD || CONFIG_KEYPAD == GIGABEAT_PAD || \
62 CONFIG_KEYPAD == GIGABEAT_S_PAD || CONFIG_KEYPAD == RECORDER_PAD || \
63 CONFIG_KEYPAD == ARCHOS_AV300_PAD || CONFIG_KEYPAD == SANSA_C100_PAD || \
64 CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
65 CONFIG_KEYPAD == SANSA_M200_PAD || CONFIG_KEYPAD == IRIVER_IFP7XX_PAD || \
66 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == PHILIPS_SA9200_PAD || \
67 CONFIG_KEYPAD == IAUDIO67_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
68 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == CREATIVEZV_PAD \
69 || CONFIG_KEYPAD == SANSA_CLIP_PAD || CONFIG_KEYPAD == LOGIK_DAX_PAD || \
70 CONFIG_KEYPAD == MEIZU_M6SL_PAD
71 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
72 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
73 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
74 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
75#elif CONFIG_KEYPAD == ONDIO_PAD
76 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
77 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
79 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
80 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
81 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
82 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
83 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
84 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
85 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
86#elif CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD
87 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
88 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
89 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
90 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
91#endif
92 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM|1)
93};
94
95const struct button_mapping pf_context_buttons[] =
96{
97#ifdef HAVE_TOUCHSCREEN
98 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
99 {PF_MENU, BUTTON_TOPLEFT, BUTTON_NONE},
100 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
101#endif
102#if CONFIG_KEYPAD == ARCHOS_AV300_PAD
103 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
104#elif CONFIG_KEYPAD == SANSA_C100_PAD
105 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
106#elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
107 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
108 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
109 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
110 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
111 CONFIG_KEYPAD == SANSA_FUZE_PAD
112 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
113/* These all use short press of BUTTON_POWER for menu, map long POWER to quit
114*/
115#elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
116 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWOND2_PAD
117 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
118#if CONFIG_KEYPAD == COWOND2_PAD
119 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
120 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
121#endif
122#elif CONFIG_KEYPAD == SANSA_E200_PAD
123 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
124#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
125 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
126#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
127 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
128 || (CONFIG_KEYPAD == IPOD_4G_PAD)
129 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
130#elif CONFIG_KEYPAD == LOGIK_DAX_PAD
131 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
132#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
133 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
134#elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
135 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
136#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
137 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
138 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
139#endif
140#if CONFIG_KEYPAD == IAUDIO_M3_PAD
141 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
142#else
143 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
144#endif
145};
146const struct button_mapping *pf_contexts[] =
147{
148 pf_context_album_scroll,
149 pf_context_buttons
150};
151
152#if LCD_DEPTH < 8
153#if LCD_DEPTH > 1
154#define N_BRIGHT(y) LCD_BRIGHTNESS(y)
155#else /* LCD_DEPTH <= 1 */
156#define N_BRIGHT(y) ((y > 127) ? 0 : 1)
157#ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
158#define PICTUREFLOW_DRMODE DRMODE_SOLID
159#else
160#define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
161#endif
162#endif /* LCD_DEPTH <= 1 */
163#define USEGSLIB
164GREY_INFO_STRUCT
165#define LCD_BUF _grey_info.buffer
166#define MYLCD(fn) grey_ ## fn
167#define G_PIX(r,g,b) \
168 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
169#define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
170#define G_BRIGHT(y) (y)
171#define BUFFER_WIDTH _grey_info.width
172#define BUFFER_HEIGHT _grey_info.height
173typedef unsigned char pix_t;
174#else /* LCD_DEPTH >= 8 */
175#define LCD_BUF rb->lcd_framebuffer
176#define MYLCD(fn) rb->lcd_ ## fn
177#define G_PIX LCD_RGBPACK
178#define N_PIX LCD_RGBPACK
179#define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
180#define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
181#define BUFFER_WIDTH LCD_WIDTH
182#define BUFFER_HEIGHT LCD_HEIGHT
183typedef fb_data pix_t;
184#endif /* LCD_DEPTH >= 8 */
185
186/* for fixed-point arithmetic, we need minimum 32-bit long
187 long long (64-bit) might be useful for multiplication and division */
188#define PFreal long
189#define PFREAL_SHIFT 10
190#define PFREAL_FACTOR (1 << PFREAL_SHIFT)
191#define PFREAL_ONE (1 << PFREAL_SHIFT)
192#define PFREAL_HALF (PFREAL_ONE >> 1)
193
194
195#define IANGLE_MAX 1024
196#define IANGLE_MASK 1023
197
198#define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
199#define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
200#define DISPLAY_HEIGHT REFLECT_TOP
201#define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
202 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
203#define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
204 (REFLECT_HEIGHT * 5))
205#define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
206#define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
207#define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
208#define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
209#define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
210
211#define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
212
213#define MAX_SLIDES_COUNT 10
214
215#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
216#define CACHE_PREFIX PLUGIN_DEMOS_DIR "/pictureflow"
217
218#define EV_EXIT 9999
219#define EV_WAKEUP 1337
220
221/* maximum number of albums */
222
223#define MAX_TRACKS 50
224#define AVG_TRACK_NAME_LENGTH 20
225
226
227#define UNIQBUF_SIZE (64*1024)
228
229#define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
230#define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
231
232/* Error return values */
233#define ERROR_NO_ALBUMS -1
234#define ERROR_BUFFER_FULL -2
235
236/* current version for cover cache */
237#define CACHE_VERSION 2
238#define CONFIG_VERSION 1
239#define CONFIG_FILE "pictureflow.cfg"
240
241/** structs we use */
242
243struct slide_data {
244 int slide_index;
245 int angle;
246 PFreal cx;
247 PFreal cy;
248 PFreal distance;
249};
250
251struct slide_cache {
252 int index; /* index of the cached slide */
253 int hid; /* handle ID of the cached slide */
254 short next; /* "next" slide, with LRU last */
255 short prev; /* "previous" slide */
256};
257
258struct album_data {
259 int name_idx;
260 long seek;
261};
262
263struct track_data {
264 int name_idx;
265 long seek;
266};
267
268struct rect {
269 int left;
270 int right;
271 int top;
272 int bottom;
273};
274
275struct load_slide_event_data {
276 int slide_index;
277 int cache_index;
278};
279
280
281struct pfraw_header {
282 int32_t width; /* bmap width in pixels */
283 int32_t height; /* bmap height in pixels */
284};
285
286const struct picture logos[]={
287 {pictureflow_logo, BMPWIDTH_pictureflow_logo, BMPHEIGHT_pictureflow_logo},
288};
289
290enum show_album_name_values { album_name_hide = 0, album_name_bottom,
291 album_name_top };
292static char* show_album_name_conf[] =
293{
294 "hide",
295 "bottom",
296 "top"
297};
298
299#define MAX_SPACING 40
300#define MAX_MARGIN 80
301
302/* config values and their defaults */
303static int slide_spacing = DISPLAY_WIDTH / 4;
304static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
305static int num_slides = 4;
306static int zoom = 100;
307static bool show_fps = false;
308static bool resize = true;
309static int cache_version = 0;
310static int show_album_name = (LCD_HEIGHT > 100)
311 ? album_name_top : album_name_bottom;
312
313static struct configdata config[] =
314{
315 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
316 NULL },
317 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
318 NULL },
319 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
320 NULL },
321 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
322 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
323 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
324 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
325 { TYPE_ENUM, 0, 2, { .int_p = &show_album_name }, "show album name",
326 show_album_name_conf }
327};
328
329#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
330
331/** below we allocate the memory we want to use **/
332
333static pix_t *buffer; /* for now it always points to the lcd framebuffer */
334static uint8_t reflect_table[REFLECT_HEIGHT];
335static struct slide_data center_slide;
336static struct slide_data left_slides[MAX_SLIDES_COUNT];
337static struct slide_data right_slides[MAX_SLIDES_COUNT];
338static int slide_frame;
339static int step;
340static int target;
341static int fade;
342static int center_index = 0; /* index of the slide that is in the center */
343static int itilt;
344static PFreal offsetX;
345static PFreal offsetY;
346static int number_of_slides;
347
348static struct slide_cache cache[SLIDE_CACHE_SIZE];
349static int cache_free;
350static int cache_used = -1;
351static int cache_left_index = -1;
352static int cache_right_index = -1;
353static int cache_center_index = -1;
354
355/* use long for aligning */
356unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
357/* queue (as array) for scheduling load_surface */
358
359static int empty_slide_hid;
360
361unsigned int thread_id;
362struct event_queue thread_q;
363
364static struct tagcache_search tcs;
365
366static struct buflib_context buf_ctx;
367
368static struct album_data *album;
369static char *album_names;
370static int album_count;
371
372static char track_names[MAX_TRACKS * AVG_TRACK_NAME_LENGTH];
373static struct track_data tracks[MAX_TRACKS];
374static int track_count;
375static int track_index;
376static int selected_track;
377static int selected_track_pulse;
378void reset_track_list(void);
379
380void * buf;
381size_t buf_size;
382
383static bool thread_is_running;
384
385static int cover_animation_keyframe;
386static int extra_fade;
387
388static int albumtxt_x = 0;
389static int albumtxt_dir = -1;
390static int prev_center_index = -1;
391
392static int start_index_track_list = 0;
393static int track_list_visible_entries = 0;
394static int track_list_y;
395static int track_list_h;
396static int track_scroll_index = 0;
397static int track_scroll_dir = 1;
398
399/*
400 Proposals for transitions:
401
402 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
403 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
404
405 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
406
407 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
408
409 TODO:
410 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
411 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
412
413 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
414*/
415enum pf_states {
416 pf_idle = 0,
417 pf_scrolling,
418 pf_cover_in,
419 pf_show_tracks,
420 pf_cover_out
421};
422
423static int pf_state;
424
425/** code */
426static inline unsigned fade_color(pix_t c, unsigned a);
427bool save_pfraw(char* filename, struct bitmap *bm);
428bool load_new_slide(void);
429int load_surface(int);
430
431static inline PFreal fmul(PFreal a, PFreal b)
432{
433 return (a*b) >> PFREAL_SHIFT;
434}
435
436/**
437 * This version preshifts each operand, which is useful when we know how many
438 * of the least significant bits will be empty, or are worried about overflow
439 * in a particular calculation
440 */
441static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
442{
443 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
444}
445
446/* ARMv5+ has a clz instruction equivalent to our function.
447 */
448#if (defined(CPU_ARM) && (ARM_ARCH > 4))
449static inline int clz(uint32_t v)
450{
451 return __builtin_clz(v);
452}
453
454/* Otherwise, use our clz, which can be inlined */
455#elif defined(CPU_COLDFIRE)
456/* This clz is based on the log2(n) implementation at
457 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
458 * A clz benchmark plugin showed this to be about 14% faster on coldfire
459 * than the LUT-based version.
460 */
461static inline int clz(uint32_t v)
462{
463 int r = 32;
464 if (v >= 0x10000)
465 {
466 v >>= 16;
467 r -= 16;
468 }
469 if (v & 0xff00)
470 {
471 v >>= 8;
472 r -= 8;
473 }
474 if (v & 0xf0)
475 {
476 v >>= 4;
477 r -= 4;
478 }
479 if (v & 0xc)
480 {
481 v >>= 2;
482 r -= 2;
483 }
484 if (v & 2)
485 {
486 v >>= 1;
487 r -= 1;
488 }
489 r -= v;
490 return r;
491}
492#else
493static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
494 0, 0, 0, 0, 0, 0, 0, 0 };
495/* This clz is based on the log2(n) implementation at
496 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
497 * It is not any faster than the one above, but trades 16B in the lookup table
498 * for a savings of 12B per each inlined call.
499 */
500static inline int clz(uint32_t v)
501{
502 int r = 28;
503 if (v >= 0x10000)
504 {
505 v >>= 16;
506 r -= 16;
507 }
508 if (v & 0xff00)
509 {
510 v >>= 8;
511 r -= 8;
512 }
513 if (v & 0xf0)
514 {
515 v >>= 4;
516 r -= 4;
517 }
518 return r + clz_lut[v];
519}
520#endif
521
522/* Return the maximum possible left shift for a signed int32, without
523 * overflow
524 */
525static inline int allowed_shift(int32_t val)
526{
527 uint32_t uval = val ^ (val >> 31);
528 return clz(uval) - 1;
529}
530
531/* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
532 * num and den before dividing.
533 */
534static inline PFreal fdiv(PFreal num, PFreal den)
535{
536 int shift = allowed_shift(num);
537 shift = MIN(PFREAL_SHIFT, shift);
538 num <<= shift;
539 den >>= PFREAL_SHIFT - shift;
540 return num / den;
541}
542
543#define fmin(a,b) (((a) < (b)) ? (a) : (b))
544#define fmax(a,b) (((a) > (b)) ? (a) : (b))
545#define fabs(a) (a < 0 ? -a : a)
546#define fbound(min,val,max) (fmax((min),fmin((max),(val))))
547
548#if CONFIG_CPU == SH7034
549/* 16*16->32 bit multiplication is a single instrcution on the SH1 */
550#define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
551#else
552#define MULUQ(a, b) ((a) * (b))
553#endif
554
555
556#if 0
557#define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
558#define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
559
560#define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
561#define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
562
563static inline PFreal fmul(PFreal a, PFreal b)
564{
565 return (a*b) >> PFREAL_SHIFT;
566}
567
568static inline PFreal fdiv(PFreal n, PFreal m)
569{
570 return (n<<(PFREAL_SHIFT))/m;
571}
572#endif
573
574/* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
575static const short sin_tab[] = {
576 0, 100, 200, 297, 392, 483, 569, 650,
577 724, 792, 851, 903, 946, 980, 1004, 1019,
578 1024, 1019, 1004, 980, 946, 903, 851, 792,
579 724, 650, 569, 483, 392, 297, 200, 100,
580 0, -100, -200, -297, -392, -483, -569, -650,
581 -724, -792, -851, -903, -946, -980, -1004, -1019,
582 -1024, -1019, -1004, -980, -946, -903, -851, -792,
583 -724, -650, -569, -483, -392, -297, -200, -100,
584 0
585};
586
587static inline PFreal fsin(int iangle)
588{
589 iangle &= IANGLE_MASK;
590
591 int i = (iangle >> 4);
592 PFreal p = sin_tab[i];
593 PFreal q = sin_tab[(i+1)];
594 PFreal g = (q - p);
595 return p + g * (iangle-i*16)/16;
596}
597
598static inline PFreal fcos(int iangle)
599{
600 return fsin(iangle + (IANGLE_MAX >> 2));
601}
602
603static inline uint32_t div255(uint32_t val)
604{
605 return ((((val >> 8) + val) >> 8) + val) >> 8;
606}
607
608#define SCALE_VAL(val,out) div255((val) * (out) + 127)
609
610static void output_row_transposed(uint32_t row, void * row_in,
611 struct scaler_context *ctx)
612{
613 pix_t *dest = (pix_t*)ctx->bm->data + row;
614 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
615#ifdef USEGSLIB
616 uint32_t *qp = (uint32_t*)row_in;
617 for (; dest < end; dest += ctx->bm->height)
618 *dest = SC_MUL((*qp++) + ctx->round, ctx->divisor);
619#else
620 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
621 uint32_t rb_mul = SCALE_VAL(ctx->divisor, 31),
622 rb_rnd = SCALE_VAL(ctx->round, 31),
623 g_mul = SCALE_VAL(ctx->divisor, 63),
624 g_rnd = SCALE_VAL(ctx->round, 63);
625 int r, g, b;
626 for (; dest < end; dest += ctx->bm->height)
627 {
628 r = SC_MUL(qp->r + rb_rnd, rb_mul);
629 g = SC_MUL(qp->g + g_rnd, g_mul);
630 b = SC_MUL(qp->b + rb_rnd, rb_mul);
631 qp++;
632 *dest = LCD_RGBPACK_LCD(r,g,b);
633 }
634#endif
635}
636
637#ifdef HAVE_LCD_COLOR
638static void output_row_transposed_fromyuv(uint32_t row, void * row_in,
639 struct scaler_context *ctx)
640{
641 pix_t *dest = (pix_t*)ctx->bm->data + row;
642 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
643 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
644 for (; dest < end; dest += ctx->bm->height)
645 {
646 unsigned r, g, b, y, u, v;
647 y = SC_MUL(qp->b + ctx->round, ctx->divisor);
648 u = SC_MUL(qp->g + ctx->round, ctx->divisor);
649 v = SC_MUL(qp->r + ctx->round, ctx->divisor);
650 qp++;
651 yuv_to_rgb(y, u, v, &r, &g, &b);
652 r = (31 * r + (r >> 3) + 127) >> 8;
653 g = (63 * g + (g >> 2) + 127) >> 8;
654 b = (31 * b + (b >> 3) + 127) >> 8;
655 *dest = LCD_RGBPACK_LCD(r, g, b);
656 }
657}
658#endif
659
660static unsigned int get_size(struct bitmap *bm)
661{
662 return bm->width * bm->height * sizeof(pix_t);
663}
664
665const struct custom_format format_transposed = {
666#ifdef HAVE_LCD_COLOR
667 .output_row = {
668 output_row_transposed,
669 output_row_transposed_fromyuv
670 },
671#else
672 .output_row = output_row_transposed,
673#endif
674 .get_size = get_size
675};
676
677static const struct button_mapping* get_context_map(int context)
678{
679 return pf_contexts[context & ~CONTEXT_CUSTOM];
680}
681
682/* Create the lookup table with the scaling values for the reflections */
683void init_reflect_table(void)
684{
685 int i;
686 for (i = 0; i < REFLECT_HEIGHT; i++)
687 reflect_table[i] =
688 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
689 (5 * REFLECT_HEIGHT);
690}
691
692/**
693 Create an index of all albums from the database.
694 Also store the album names so we can access them later.
695 */
696int create_album_index(void)
697{
698 buf_size -= UNIQBUF_SIZE * sizeof(long);
699 long *uniqbuf = (long *)(buf_size + (char *)buf);
700 album = ((struct album_data *)uniqbuf) - 1;
701 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
702 album_count = 0;
703 rb->tagcache_search(&tcs, tag_album);
704 rb->tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
705 unsigned int l, old_l = 0;
706 album_names = buf;
707 album[0].name_idx = 0;
708 while (rb->tagcache_get_next(&tcs))
709 {
710 buf_size -= sizeof(struct album_data);
711 l = rb->strlen(tcs.result) + 1;
712 if ( album_count > 0 )
713 album[-album_count].name_idx = album[1-album_count].name_idx + old_l;
714
715 if ( l > buf_size )
716 /* not enough memory */
717 return ERROR_BUFFER_FULL;
718
719 rb->strcpy(buf, tcs.result);
720 buf_size -= l;
721 buf = l + (char *)buf;
722 album[-album_count].seek = tcs.result_seek;
723 old_l = l;
724 album_count++;
725 }
726 rb->tagcache_search_finish(&tcs);
727 ALIGN_BUFFER(buf, buf_size, 4);
728 int i;
729 struct album_data* tmp_album = (struct album_data*)buf;
730 for (i = album_count - 1; i >= 0; i--)
731 tmp_album[i] = album[-i];
732 album = tmp_album;
733 buf = album + album_count;
734 buf_size += UNIQBUF_SIZE * sizeof(long);
735 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
736}
737
738/**
739 Return a pointer to the album name of the given slide_index
740 */
741char* get_album_name(const int slide_index)
742{
743 return album_names + album[slide_index].name_idx;
744}
745
746/**
747 Return a pointer to the track name of the active album
748 create_track_index has to be called first.
749 */
750char* get_track_name(const int track_index)
751{
752 if ( track_index < track_count )
753 return track_names + tracks[track_index].name_idx;
754 return 0;
755}
756
757/**
758 Create the track index of the given slide_index.
759 */
760int create_track_index(const int slide_index)
761{
762 if ( slide_index == track_index ) {
763 return -1;
764 }
765
766 if (!rb->tagcache_search(&tcs, tag_title))
767 return -1;
768
769 int ret = 0;
770 char temp_titles[MAX_TRACKS][AVG_TRACK_NAME_LENGTH*4];
771 int temp_seeks[MAX_TRACKS];
772
773 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
774 track_count=0;
775 int string_index = 0;
776 int l, track_num, heighest_index = 0;
777
778 for(l=0;l<MAX_TRACKS;l++)
779 temp_titles[l][0] = '\0';
780 while (rb->tagcache_get_next(&tcs) && track_count < MAX_TRACKS)
781 {
782 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber) - 1;
783 if (track_num >= 0)
784 {
785 rb->snprintf(temp_titles[track_num],sizeof(temp_titles[track_num]),
786 "%d: %s", track_num+1, tcs.result);
787 temp_seeks[track_num] = tcs.result_seek;
788 }
789 else
790 {
791 track_num = 0;
792 while (temp_titles[track_num][0] != '\0')
793 track_num++;
794 rb->strcpy(temp_titles[track_num], tcs.result);
795 temp_seeks[track_num] = tcs.result_seek;
796 }
797 if (track_num > heighest_index)
798 heighest_index = track_num;
799 track_count++;
800 }
801
802 rb->tagcache_search_finish(&tcs);
803 track_index = slide_index;
804
805 /* now fix the track list order */
806 l = 0;
807 track_count = 0;
808 while (l <= heighest_index &&
809 string_index < MAX_TRACKS*AVG_TRACK_NAME_LENGTH)
810 {
811 if (temp_titles[l][0] != '\0')
812 {
813 rb->strcpy(track_names + string_index, temp_titles[l]);
814 tracks[track_count].name_idx = string_index;
815 tracks[track_count].seek = temp_seeks[l];
816 string_index += rb->strlen(temp_titles[l]) + 1;
817 track_count++;
818 }
819 l++;
820 }
821 if (ret != 0)
822 return ret;
823 else
824 return (track_count > 0) ? 0 : -1;
825}
826
827/**
828 Determine filename of the album art for the given slide_index and
829 store the result in buf.
830 The algorithm looks for the first track of the given album uses
831 find_albumart to find the filename.
832 */
833bool get_albumart_for_index_from_db(const int slide_index, char *buf,
834 int buflen)
835{
836 if ( slide_index == -1 )
837 {
838 rb->strncpy( buf, EMPTY_SLIDE, buflen );
839 }
840
841 if (!rb->tagcache_search(&tcs, tag_filename))
842 return false;
843
844 bool result;
845 /* find the first track of the album */
846 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
847
848 if ( rb->tagcache_get_next(&tcs) ) {
849 struct mp3entry id3;
850 int fd;
851
852 fd = rb->open(tcs.result, O_RDONLY);
853 rb->get_metadata(&id3, fd, tcs.result);
854 rb->close(fd);
855 if ( search_albumart_files(&id3, "", buf, buflen) )
856 result = true;
857 else
858 result = false;
859 }
860 else {
861 /* did not find a matching track */
862 result = false;
863 }
864 rb->tagcache_search_finish(&tcs);
865 return result;
866}
867
868/**
869 Draw the PictureFlow logo
870 */
871void draw_splashscreen(void)
872{
873 struct screen* display = rb->screens[0];
874 const struct picture* logo = &(logos[display->screen_type]);
875
876#if LCD_DEPTH > 1
877 rb->lcd_set_background(N_BRIGHT(0));
878 rb->lcd_set_foreground(N_BRIGHT(255));
879#else
880 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
881#endif
882 rb->lcd_clear_display();
883
884#if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
885 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
886 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
887 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
888#else
889 picture_draw(display, logo, (LCD_WIDTH - logo->width) / 2, 10);
890#endif
891
892 rb->lcd_update();
893}
894
895
896/**
897 Draw a simple progress bar
898 */
899void draw_progressbar(int step)
900{
901 int txt_w, txt_h;
902 const int bar_height = 22;
903 const int w = LCD_WIDTH - 20;
904 const int x = 10;
905
906 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
907
908 int y = (LCD_HEIGHT - txt_h)/2;
909
910 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
911 y += (txt_h + 5);
912
913#if LCD_DEPTH > 1
914 rb->lcd_set_foreground(N_BRIGHT(100));
915#endif
916 rb->lcd_drawrect(x, y, w+2, bar_height);
917#if LCD_DEPTH > 1
918 rb->lcd_set_foreground(N_PIX(165, 231, 82));
919#endif
920
921 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
922#if LCD_DEPTH > 1
923 rb->lcd_set_foreground(N_BRIGHT(255));
924#endif
925 rb->lcd_update();
926 rb->yield();
927}
928
929/**
930 Precomupte the album art images and store them in CACHE_PREFIX.
931 */
932bool create_albumart_cache(void)
933{
934 int ret;
935
936 int i, slides = 0;
937 struct bitmap input_bmp;
938
939 char pfraw_file[MAX_PATH];
940 char albumart_file[MAX_PATH];
941 unsigned int format = FORMAT_NATIVE;
942 cache_version = 0;
943 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
944 if (resize)
945 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
946 for (i=0; i < album_count; i++)
947 {
948 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%d.pfraw",
949 i);
950 /* delete existing cache, so it's a true rebuild */
951 if(rb->file_exists(pfraw_file))
952 rb->remove(pfraw_file);
953 draw_progressbar(i);
954 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
955 continue;
956
957 input_bmp.data = buf;
958 input_bmp.width = DISPLAY_WIDTH;
959 input_bmp.height = DISPLAY_HEIGHT;
960 ret = read_image_file(albumart_file, &input_bmp,
961 buf_size, format, &format_transposed);
962 if (ret <= 0) {
963 rb->splash(HZ, "Could not read bmp");
964 continue; /* skip missing/broken files */
965 }
966 if (!save_pfraw(pfraw_file, &input_bmp))
967 {
968 rb->splash(HZ, "Could not write bmp");
969 }
970 slides++;
971 if ( rb->button_get(false) == PF_MENU ) return false;
972 }
973 if ( slides == 0 ) {
974 /* Warn the user that we couldn't find any albumart */
975 rb->splash(2*HZ, "No album art found");
976 return false;
977 }
978 return true;
979}
980
981/**
982 Thread used for loading and preparing bitmaps in the background
983 */
984void thread(void)
985{
986 long sleep_time = 5 * HZ;
987 struct queue_event ev;
988 while (1) {
989 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
990 switch (ev.id) {
991 case EV_EXIT:
992 return;
993 case EV_WAKEUP:
994 /* we just woke up */
995 break;
996 }
997 while ( load_new_slide() ) {
998 rb->yield();
999 switch (ev.id) {
1000 case EV_EXIT:
1001 return;
1002 }
1003 }
1004 }
1005}
1006
1007
1008/**
1009 End the thread by posting the EV_EXIT event
1010 */
1011void end_pf_thread(void)
1012{
1013 if ( thread_is_running ) {
1014 rb->queue_post(&thread_q, EV_EXIT, 0);
1015 rb->thread_wait(thread_id);
1016 /* remove the thread's queue from the broadcast list */
1017 rb->queue_delete(&thread_q);
1018 thread_is_running = false;
1019 }
1020
1021}
1022
1023
1024/**
1025 Create the thread an setup the event queue
1026 */
1027bool create_pf_thread(void)
1028{
1029 /* put the thread's queue in the bcast list */
1030 rb->queue_init(&thread_q, true);
1031 if ((thread_id = rb->create_thread(
1032 thread,
1033 thread_stack,
1034 sizeof(thread_stack),
1035 0,
1036 "Picture load thread"
1037 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1038 PRIORITY_REALTIME + 1))
1039 IF_COP(, CPU)
1040 )
1041 ) == 0) {
1042 return false;
1043 }
1044 thread_is_running = true;
1045 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1046 return true;
1047}
1048
1049/**
1050 Safe the given bitmap as filename in the pfraw format
1051 */
1052bool save_pfraw(char* filename, struct bitmap *bm)
1053{
1054 struct pfraw_header bmph;
1055 bmph.width = bm->width;
1056 bmph.height = bm->height;
1057 int fh = rb->creat( filename );
1058 if( fh < 0 ) return false;
1059 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1060 int y;
1061 for( y = 0; y < bm->height; y++ )
1062 {
1063 pix_t *d = (pix_t*)( bm->data ) + (y*bm->width);
1064 rb->write( fh, d, sizeof( pix_t ) * bm->width );
1065 }
1066 rb->close( fh );
1067 return true;
1068}
1069
1070
1071/*
1072 * The following functions implement the linked-list-in-array used to manage
1073 * the LRU cache of slides, and the list of free cache slots.
1074 */
1075
1076#define seek_right_while(start, cond) \
1077({ \
1078 int ind_, next_ = (start); \
1079 do { \
1080 ind_ = next_; \
1081 next_ = cache[ind_].next; \
1082 } while (next_ != cache_used && (cond)); \
1083 ind_; \
1084})
1085
1086#define seek_left_while(start, cond) \
1087({ \
1088 int ind_, next_ = (start); \
1089 do { \
1090 ind_ = next_; \
1091 next_ = cache[ind_].prev; \
1092 } while (ind_ != cache_used && (cond)); \
1093 ind_; \
1094})
1095
1096/**
1097 Pop the given item from the linked list starting at *head, returning the next
1098 item, or -1 if the list is now empty.
1099*/
1100static inline int lla_pop_item (int *head, int i)
1101{
1102 int prev = cache[i].prev;
1103 int next = cache[i].next;
1104 if (i == next)
1105 {
1106 *head = -1;
1107 return -1;
1108 }
1109 else if (i == *head)
1110 *head = next;
1111 cache[next].prev = prev;
1112 cache[prev].next = next;
1113 return next;
1114}
1115
1116
1117/**
1118 Pop the head item from the list starting at *head, returning the index of the
1119 item, or -1 if the list is already empty.
1120*/
1121static inline int lla_pop_head (int *head)
1122{
1123 int i = *head;
1124 if (i != -1)
1125 lla_pop_item(head, i);
1126 return i;
1127}
1128
1129/**
1130 Insert the item at index i before the one at index p.
1131*/
1132static inline void lla_insert (int i, int p)
1133{
1134 int next = p;
1135 int prev = cache[next].prev;
1136 cache[next].prev = i;
1137 cache[prev].next = i;
1138 cache[i].next = next;
1139 cache[i].prev = prev;
1140}
1141
1142
1143/**
1144 Insert the item at index i at the end of the list starting at *head.
1145*/
1146static inline void lla_insert_tail (int *head, int i)
1147{
1148 if (*head == -1)
1149 {
1150 *head = i;
1151 cache[i].next = i;
1152 cache[i].prev = i;
1153 } else
1154 lla_insert(i, *head);
1155}
1156
1157/**
1158 Insert the item at index i before the one at index p.
1159*/
1160static inline void lla_insert_after(int i, int p)
1161{
1162 p = cache[p].next;
1163 lla_insert(i, p);
1164}
1165
1166
1167/**
1168 Insert the item at index i before the one at index p in the list starting at
1169 *head
1170*/
1171static inline void lla_insert_before(int *head, int i, int p)
1172{
1173 lla_insert(i, p);
1174 if (*head == p)
1175 *head = i;
1176}
1177
1178
1179/**
1180 Free the used slide at index i, and its buffer, and move it to the free
1181 slides list.
1182*/
1183static inline void free_slide(int i)
1184{
1185 if (cache[i].hid != empty_slide_hid)
1186 buflib_free(&buf_ctx, cache[i].hid);
1187 cache[i].index = -1;
1188 lla_pop_item(&cache_used, i);
1189 lla_insert_tail(&cache_free, i);
1190 if (cache_used == -1)
1191 {
1192 cache_right_index = -1;
1193 cache_left_index = -1;
1194 cache_center_index = -1;
1195 }
1196}
1197
1198
1199/**
1200 Free one slide ranked above the given priority. If no such slide can be found,
1201 return false.
1202*/
1203static inline int free_slide_prio(int prio)
1204{
1205 if (cache_used == -1)
1206 return false;
1207 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1208 int prio_l = cache[l].index < center_index ?
1209 center_index - cache[l].index : 0;
1210 int prio_r = cache[r].index > center_index ?
1211 cache[r].index - center_index : 0;
1212 if (prio_l > prio_r)
1213 {
1214 i = l;
1215 prio_max = prio_l;
1216 } else {
1217 i = r;
1218 prio_max = prio_r;
1219 }
1220 if (prio_max > prio)
1221 {
1222 if (i == cache_left_index)
1223 cache_left_index = cache[i].next;
1224 if (i == cache_right_index)
1225 cache_right_index = cache[i].prev;
1226 free_slide(i);
1227 return true;
1228 } else
1229 return false;
1230}
1231
1232/**
1233 Read the pfraw image given as filename and return the hid of the buffer
1234 */
1235int read_pfraw(char* filename, int prio)
1236{
1237 struct pfraw_header bmph;
1238 int fh = rb->open(filename, O_RDONLY);
1239 if( fh < 0 )
1240 return empty_slide_hid;
1241 else
1242 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1243
1244 int size = sizeof(struct bitmap) + sizeof( pix_t ) *
1245 bmph.width * bmph.height;
1246
1247 int hid;
1248 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1249
1250 if (!hid) {
1251 rb->close( fh );
1252 return 0;
1253 }
1254
1255 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1256
1257 bm->width = bmph.width;
1258 bm->height = bmph.height;
1259 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1260
1261 int y;
1262 for( y = 0; y < bm->height; y++ )
1263 {
1264 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1265 data += bm->width;
1266 }
1267 rb->close( fh );
1268 return hid;
1269}
1270
1271
1272/**
1273 Load the surface for the given slide_index into the cache at cache_index.
1274 */
1275static inline bool load_and_prepare_surface(const int slide_index,
1276 const int cache_index,
1277 const int prio)
1278{
1279 char tmp_path_name[MAX_PATH+1];
1280 rb->snprintf(tmp_path_name, sizeof(tmp_path_name), CACHE_PREFIX "/%d.pfraw",
1281 slide_index);
1282
1283 int hid = read_pfraw(tmp_path_name, prio);
1284 if (!hid)
1285 return false;
1286
1287 cache[cache_index].hid = hid;
1288
1289 if ( cache_index < SLIDE_CACHE_SIZE ) {
1290 cache[cache_index].index = slide_index;
1291 }
1292
1293 return true;
1294}
1295
1296
1297/**
1298 Load the "next" slide that we can load, freeing old slides if needed, provided
1299 that they are further from center_index than the current slide
1300*/
1301bool load_new_slide(void)
1302{
1303 int i = -1;
1304 if (cache_center_index != -1)
1305 {
1306 int next, prev;
1307 if (cache[cache_center_index].index != center_index)
1308 {
1309 if (cache[cache_center_index].index < center_index)
1310 {
1311 cache_center_index = seek_right_while(cache_center_index,
1312 cache[next_].index <= center_index);
1313 prev = cache_center_index;
1314 next = cache[cache_center_index].next;
1315 }
1316 else
1317 {
1318 cache_center_index = seek_left_while(cache_center_index,
1319 cache[next_].index >= center_index);
1320 next = cache_center_index;
1321 prev = cache[cache_center_index].prev;
1322 }
1323 if (cache[cache_center_index].index != center_index)
1324 {
1325 if (cache_free == -1)
1326 free_slide_prio(0);
1327 i = lla_pop_head(&cache_free);
1328 if (!load_and_prepare_surface(center_index, i, 0))
1329 goto fail_and_refree;
1330 if (cache[next].index == -1)
1331 {
1332 if (cache[prev].index == -1)
1333 goto insert_first_slide;
1334 else
1335 next = cache[prev].next;
1336 }
1337 lla_insert(i, next);
1338 if (cache[i].index < cache[cache_used].index)
1339 cache_used = i;
1340 cache_center_index = i;
1341 cache_left_index = i;
1342 cache_right_index = i;
1343 return true;
1344 }
1345 }
1346 if (cache[cache_left_index].index >
1347 cache[cache_center_index].index)
1348 cache_left_index = cache_center_index;
1349 if (cache[cache_right_index].index <
1350 cache[cache_center_index].index)
1351 cache_right_index = cache_center_index;
1352 cache_left_index = seek_left_while(cache_left_index,
1353 cache[ind_].index - 1 == cache[next_].index);
1354 cache_right_index = seek_right_while(cache_right_index,
1355 cache[ind_].index - 1 == cache[next_].index);
1356 int prio_l = cache[cache_center_index].index -
1357 cache[cache_left_index].index + 1;
1358 int prio_r = cache[cache_right_index].index -
1359 cache[cache_center_index].index + 1;
1360 if ((prio_l < prio_r ||
1361 cache[cache_right_index].index >= number_of_slides) &&
1362 cache[cache_left_index].index > 0)
1363 {
1364 if (cache_free == -1 && !free_slide_prio(prio_l))
1365 return false;
1366 i = lla_pop_head(&cache_free);
1367 if (load_and_prepare_surface(cache[cache_left_index].index
1368 - 1, i, prio_l))
1369 {
1370 lla_insert_before(&cache_used, i, cache_left_index);
1371 cache_left_index = i;
1372 return true;
1373 }
1374 } else if(cache[cache_right_index].index < number_of_slides - 1)
1375 {
1376 if (cache_free == -1 && !free_slide_prio(prio_r))
1377 return false;
1378 i = lla_pop_head(&cache_free);
1379 if (load_and_prepare_surface(cache[cache_right_index].index
1380 + 1, i, prio_r))
1381 {
1382 lla_insert_after(i, cache_right_index);
1383 cache_right_index = i;
1384 return true;
1385 }
1386 }
1387 } else {
1388 i = lla_pop_head(&cache_free);
1389 if (load_and_prepare_surface(center_index, i, 0))
1390 {
1391insert_first_slide:
1392 cache[i].next = i;
1393 cache[i].prev = i;
1394 cache_center_index = i;
1395 cache_left_index = i;
1396 cache_right_index = i;
1397 cache_used = i;
1398 return true;
1399 }
1400 }
1401fail_and_refree:
1402 if (i != -1)
1403 {
1404 lla_insert_tail(&cache_free, i);
1405 }
1406 return false;
1407}
1408
1409
1410/**
1411 Get a slide from the buffer
1412 */
1413static inline struct dim *get_slide(const int hid)
1414{
1415 if (!hid)
1416 return NULL;
1417
1418 struct dim *bmp;
1419
1420 bmp = buflib_get_data(&buf_ctx, hid);
1421
1422 return bmp;
1423}
1424
1425
1426/**
1427 Return the requested surface
1428*/
1429static inline struct dim *surface(const int slide_index)
1430{
1431 if (slide_index < 0)
1432 return 0;
1433 if (slide_index >= number_of_slides)
1434 return 0;
1435 int i;
1436 if ((i = cache_used ) != -1)
1437 {
1438 do {
1439 if (cache[i].index == slide_index)
1440 return get_slide(cache[i].hid);
1441 i = cache[i].next;
1442 } while (i != cache_used);
1443 }
1444 return get_slide(empty_slide_hid);
1445}
1446
1447/**
1448 adjust slides so that they are in "steady state" position
1449 */
1450void reset_slides(void)
1451{
1452 center_slide.angle = 0;
1453 center_slide.cx = 0;
1454 center_slide.cy = 0;
1455 center_slide.distance = 0;
1456 center_slide.slide_index = center_index;
1457
1458 int i;
1459 for (i = 0; i < num_slides; i++) {
1460 struct slide_data *si = &left_slides[i];
1461 si->angle = itilt;
1462 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1463 si->cy = offsetY;
1464 si->slide_index = center_index - 1 - i;
1465 si->distance = 0;
1466 }
1467
1468 for (i = 0; i < num_slides; i++) {
1469 struct slide_data *si = &right_slides[i];
1470 si->angle = -itilt;
1471 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1472 si->cy = offsetY;
1473 si->slide_index = center_index + 1 + i;
1474 si->distance = 0;
1475 }
1476}
1477
1478
1479/**
1480 Updates look-up table and other stuff necessary for the rendering.
1481 Call this when the viewport size or slide dimension is changed.
1482 *
1483 * To calculate the offset that will provide the proper margin, we use the same
1484 * projection used to render the slides. The solution for xc, the slide center,
1485 * is:
1486 * xp * (zo + xs * sin(r))
1487 * xc = xp - xs * cos(r) + ───────────────────────
1488 * z
1489 * TODO: support moving the side slides toward or away from the camera
1490 */
1491void recalc_offsets(void)
1492{
1493 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1494 PFreal zo;
1495 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1496 PFREAL_ONE) * zoom / 100;
1497 PFreal cosr, sinr;
1498
1499 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1500 cosr = fcos(-itilt);
1501 sinr = fsin(-itilt);
1502 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1503 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1504 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1505 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1506 / CAM_DIST;
1507 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1508}
1509
1510
1511/**
1512 Fade the given color by spreading the fb_data (ushort)
1513 to an uint, multiply and compress the result back to a ushort.
1514 */
1515#if (LCD_PIXELFORMAT == RGB565SWAPPED)
1516static inline unsigned fade_color(pix_t c, unsigned a)
1517{
1518 unsigned int result;
1519 c = swap16(c);
1520 a = (a + 2) & 0x1fc;
1521 result = ((c & 0xf81f) * a) & 0xf81f00;
1522 result |= ((c & 0x7e0) * a) & 0x7e000;
1523 result >>= 8;
1524 return swap16(result);
1525}
1526#elif LCD_PIXELFORMAT == RGB565
1527static inline unsigned fade_color(pix_t c, unsigned a)
1528{
1529 unsigned int result;
1530 a = (a + 2) & 0x1fc;
1531 result = ((c & 0xf81f) * a) & 0xf81f00;
1532 result |= ((c & 0x7e0) * a) & 0x7e000;
1533 result >>= 8;
1534 return result;
1535}
1536#else
1537static inline unsigned fade_color(pix_t c, unsigned a)
1538{
1539 unsigned val = c;
1540 return MULUQ(val, a) >> 8;
1541}
1542#endif
1543
1544/**
1545 * Render a single slide
1546 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1547 * on the slide from its center, zo is the slide's depth offset from the plane
1548 * of the display, r is the angle at which the slide is tilted, and xp is the
1549 * point on the display corresponding to xs on the slide, the projection
1550 * formulas are:
1551 *
1552 * z * (xc + xs * cos(r))
1553 * xp = ──────────────────────
1554 * z + zo + xs * sin(r)
1555 *
1556 * z * (xc - xp) - xp * zo
1557 * xs = ────────────────────────
1558 * xp * sin(r) - z * cos(r)
1559 *
1560 * We use the xp projection once, to find the left edge of the slide on the
1561 * display. From there, we use the xs reverse projection to find the horizontal
1562 * offset from the slide center of each column on the screen, until we reach
1563 * the right edge of the slide, or the screen. The reverse projection can be
1564 * optimized by saving the numerator and denominator of the fraction, which can
1565 * then be incremented by (z + zo) and sin(r) respectively.
1566 */
1567void render_slide(struct slide_data *slide, const int alpha)
1568{
1569 struct dim *bmp = surface(slide->slide_index);
1570 if (!bmp) {
1571 return;
1572 }
1573 if (slide->angle > 255 || slide->angle < -255)
1574 return;
1575 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1576
1577 const int sw = bmp->width;
1578 const int sh = bmp->height;
1579 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1580 const int w = LCD_WIDTH;
1581
1582 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1583
1584 if (alpha == 256) { /* opaque -> copy table */
1585 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1586 } else { /* precalculate faded table */
1587 int i, lalpha;
1588 for (i = 0; i < REFLECT_HEIGHT; i++) {
1589 lalpha = reflect_table[i];
1590 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1591 }
1592 }
1593
1594 PFreal cosr = fcos(slide->angle);
1595 PFreal sinr = fsin(slide->angle);
1596 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1597 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1598 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1599 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1600 (CAM_DIST_R + zo + fmul(xs,sinr)));
1601
1602 /* Since we're finding the screen position of the left edge of the slide,
1603 * we round up.
1604 */
1605 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1606 >> PFREAL_SHIFT;
1607 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1608 if (xi >= w) {
1609 return;
1610 }
1611 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1612 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1613 xs = fdiv(xsnum, xsden);
1614
1615 xsnumi = -CAM_DIST_R - zo;
1616 xsdeni = sinr;
1617 int x;
1618 int dy = PFREAL_ONE;
1619 for (x = xi; x < w; x++) {
1620 int column = (xs - slide_left) / PFREAL_ONE;
1621 if (column >= sw)
1622 break;
1623 if (zo || slide->angle)
1624 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1625
1626 const pix_t *ptr = &src[column * bmp->height];
1627 const int pixelstep = BUFFER_WIDTH;
1628
1629 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1630 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1631 pix_t *pixel = &buffer[((LCD_HEIGHT/2)-1)*BUFFER_WIDTH + x];
1632
1633 if (alpha == 256) {
1634 while (p >= plim) {
1635 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1636 p -= dy;
1637 pixel -= pixelstep;
1638 }
1639 } else {
1640 while (p >= plim) {
1641 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1642 p -= dy;
1643 pixel -= pixelstep;
1644 }
1645 }
1646 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1647 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1648 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1649 p + (LCD_HEIGHT/2) * dy);
1650 pixel = &buffer[(LCD_HEIGHT/2)*BUFFER_WIDTH + x];
1651
1652 if (alpha == 256) {
1653 while (p < plim) {
1654 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1655 p += dy;
1656 pixel += pixelstep;
1657 }
1658 } else {
1659 while (p < plim) {
1660 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1661 p += dy;
1662 pixel += pixelstep;
1663 }
1664 }
1665 while (p < plim2) {
1666 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1667 int lalpha = reftab[ty];
1668 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1669 p += dy;
1670 pixel += pixelstep;
1671 }
1672
1673 if (zo || slide->angle)
1674 {
1675 xsnum += xsnumi;
1676 xsden += xsdeni;
1677 xs = fdiv(xsnum, xsden);
1678 } else
1679 xs += PFREAL_ONE;
1680
1681 }
1682 /* let the music play... */
1683 rb->yield();
1684 return;
1685}
1686
1687
1688/**
1689 Jump the the given slide_index
1690 */
1691static inline void set_current_slide(const int slide_index)
1692{
1693 int old_center_index = center_index;
1694 step = 0;
1695 center_index = fbound(slide_index, 0, number_of_slides - 1);
1696 if (old_center_index != center_index)
1697 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1698 target = center_index;
1699 slide_frame = slide_index << 16;
1700 reset_slides();
1701}
1702
1703/**
1704 Start the animation for changing slides
1705 */
1706void start_animation(void)
1707{
1708 step = (target < center_slide.slide_index) ? -1 : 1;
1709 pf_state = pf_scrolling;
1710}
1711
1712/**
1713 Go to the previous slide
1714 */
1715void show_previous_slide(void)
1716{
1717 if (step == 0) {
1718 if (center_index > 0) {
1719 target = center_index - 1;
1720 start_animation();
1721 }
1722 } else if ( step > 0 ) {
1723 target = center_index;
1724 start_animation();
1725 } else {
1726 target = fmax(0, center_index - 2);
1727 }
1728}
1729
1730
1731/**
1732 Go to the next slide
1733 */
1734void show_next_slide(void)
1735{
1736 if (step == 0) {
1737 if (center_index < number_of_slides - 1) {
1738 target = center_index + 1;
1739 start_animation();
1740 }
1741 } else if ( step < 0 ) {
1742 target = center_index;
1743 start_animation();
1744 } else {
1745 target = fmin(center_index + 2, number_of_slides - 1);
1746 }
1747}
1748
1749
1750/**
1751 Render the slides. Updates only the offscreen buffer.
1752*/
1753void render_all_slides(void)
1754{
1755 MYLCD(set_background)(G_BRIGHT(0));
1756 /* TODO: Optimizes this by e.g. invalidating rects */
1757 MYLCD(clear_display)();
1758
1759 int nleft = num_slides;
1760 int nright = num_slides;
1761
1762 int index;
1763 if (step == 0) {
1764 /* no animation, boring plain rendering */
1765 for (index = nleft - 2; index >= 0; index--) {
1766 int alpha = (index < nleft - 2) ? 256 : 128;
1767 alpha -= extra_fade;
1768 if (alpha > 0 )
1769 render_slide(&left_slides[index], alpha);
1770 }
1771 for (index = nright - 2; index >= 0; index--) {
1772 int alpha = (index < nright - 2) ? 256 : 128;
1773 alpha -= extra_fade;
1774 if (alpha > 0 )
1775 render_slide(&right_slides[index], alpha);
1776 }
1777 } else {
1778 /* the first and last slide must fade in/fade out */
1779 for (index = nleft - 1; index >= 0; index--) {
1780 int alpha = 256;
1781 if (index == nleft - 1)
1782 alpha = (step > 0) ? 0 : 128 - fade / 2;
1783 if (index == nleft - 2)
1784 alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
1785 if (index == nleft - 3)
1786 alpha = (step > 0) ? 256 - fade / 2 : 256;
1787 render_slide(&left_slides[index], alpha);
1788 }
1789 for (index = nright - 1; index >= 0; index--) {
1790 int alpha = (index < nright - 2) ? 256 : 128;
1791 if (index == nright - 1)
1792 alpha = (step > 0) ? fade / 2 : 0;
1793 if (index == nright - 2)
1794 alpha = (step > 0) ? 128 + fade / 2 : fade / 2;
1795 if (index == nright - 3)
1796 alpha = (step > 0) ? 256 : 128 + fade / 2;
1797 render_slide(&right_slides[index], alpha);
1798 }
1799 }
1800 render_slide(&center_slide, 256);
1801}
1802
1803
1804/**
1805 Updates the animation effect. Call this periodically from a timer.
1806*/
1807void update_scroll_animation(void)
1808{
1809 if (step == 0)
1810 return;
1811
1812 int speed = 16384;
1813 int i;
1814
1815 /* deaccelerate when approaching the target */
1816 if (true) {
1817 const int max = 2 * 65536;
1818
1819 int fi = slide_frame;
1820 fi -= (target << 16);
1821 if (fi < 0)
1822 fi = -fi;
1823 fi = fmin(fi, max);
1824
1825 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
1826 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
1827 }
1828
1829 slide_frame += speed * step;
1830
1831 int index = slide_frame >> 16;
1832 int pos = slide_frame & 0xffff;
1833 int neg = 65536 - pos;
1834 int tick = (step < 0) ? neg : pos;
1835 PFreal ftick = (tick * PFREAL_ONE) >> 16;
1836
1837 /* the leftmost and rightmost slide must fade away */
1838 fade = pos / 256;
1839
1840 if (step < 0)
1841 index++;
1842 if (center_index != index) {
1843 center_index = index;
1844 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1845 slide_frame = index << 16;
1846 center_slide.slide_index = center_index;
1847 for (i = 0; i < num_slides; i++)
1848 left_slides[i].slide_index = center_index - 1 - i;
1849 for (i = 0; i < num_slides; i++)
1850 right_slides[i].slide_index = center_index + 1 + i;
1851 }
1852
1853 center_slide.angle = (step * tick * itilt) >> 16;
1854 center_slide.cx = -step * fmul(offsetX, ftick);
1855 center_slide.cy = fmul(offsetY, ftick);
1856
1857 if (center_index == target) {
1858 reset_slides();
1859 pf_state = pf_idle;
1860 step = 0;
1861 fade = 256;
1862 return;
1863 }
1864
1865 for (i = 0; i < num_slides; i++) {
1866 struct slide_data *si = &left_slides[i];
1867 si->angle = itilt;
1868 si->cx =
1869 -(offsetX + slide_spacing * i * PFREAL_ONE + step
1870 * slide_spacing * ftick);
1871 si->cy = offsetY;
1872 }
1873
1874 for (i = 0; i < num_slides; i++) {
1875 struct slide_data *si = &right_slides[i];
1876 si->angle = -itilt;
1877 si->cx =
1878 offsetX + slide_spacing * i * PFREAL_ONE - step
1879 * slide_spacing * ftick;
1880 si->cy = offsetY;
1881 }
1882
1883 if (step > 0) {
1884 PFreal ftick = (neg * PFREAL_ONE) >> 16;
1885 right_slides[0].angle = -(neg * itilt) >> 16;
1886 right_slides[0].cx = fmul(offsetX, ftick);
1887 right_slides[0].cy = fmul(offsetY, ftick);
1888 } else {
1889 PFreal ftick = (pos * PFREAL_ONE) >> 16;
1890 left_slides[0].angle = (pos * itilt) >> 16;
1891 left_slides[0].cx = -fmul(offsetX, ftick);
1892 left_slides[0].cy = fmul(offsetY, ftick);
1893 }
1894
1895 /* must change direction ? */
1896 if (target < index)
1897 if (step > 0)
1898 step = -1;
1899 if (target > index)
1900 if (step < 0)
1901 step = 1;
1902}
1903
1904
1905/**
1906 Cleanup the plugin
1907*/
1908void cleanup(void *parameter)
1909{
1910 (void) parameter;
1911 /* Turn on backlight timeout (revert to settings) */
1912 backlight_use_settings(); /* backlight control in lib/helper.c */
1913
1914#ifdef USEGSLIB
1915 grey_release();
1916#endif
1917}
1918
1919/**
1920 Create the "?" slide, that is shown while loading
1921 or when no cover was found.
1922 */
1923int create_empty_slide(bool force)
1924{
1925 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1926 struct bitmap input_bmp;
1927 int ret;
1928 input_bmp.width = DISPLAY_WIDTH;
1929 input_bmp.height = DISPLAY_HEIGHT;
1930#if LCD_DEPTH > 1
1931 input_bmp.format = FORMAT_NATIVE;
1932#endif
1933 input_bmp.data = (char*)buf;
1934 ret = scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1935 buf_size,
1936 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1937 &format_transposed);
1938 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1939 return false;
1940 }
1941
1942 return true;
1943}
1944
1945/**
1946 Shows the album name setting menu
1947*/
1948int album_name_menu(void)
1949{
1950 int selection = show_album_name;
1951
1952 MENUITEM_STRINGLIST(album_name_menu,"Show album title",NULL,
1953 "Hide album title", "Show at the bottom", "Show at the top");
1954 rb->do_menu(&album_name_menu, &selection, NULL, false);
1955
1956 show_album_name = selection;
1957 return GO_TO_PREVIOUS;
1958}
1959
1960/**
1961 Shows the settings menu
1962 */
1963int settings_menu(void)
1964{
1965 int selection = 0;
1966 bool old_val;
1967
1968 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
1969 "Spacing", "Centre margin", "Number of slides", "Zoom",
1970 "Show album title", "Resize Covers", "Rebuild cache");
1971
1972 do {
1973 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
1974 switch(selection) {
1975 case 0:
1976 rb->set_bool("Show FPS", &show_fps);
1977 reset_track_list();
1978 break;
1979
1980 case 1:
1981 rb->set_int("Spacing between slides", "", 1,
1982 &slide_spacing,
1983 NULL, 1, 0, 100, NULL );
1984 recalc_offsets();
1985 reset_slides();
1986 break;
1987
1988 case 2:
1989 rb->set_int("Centre margin", "", 1,
1990 &center_margin,
1991 NULL, 1, 0, 80, NULL );
1992 recalc_offsets();
1993 reset_slides();
1994 break;
1995
1996 case 3:
1997 rb->set_int("Number of slides", "", 1, &num_slides,
1998 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
1999 recalc_offsets();
2000 reset_slides();
2001 break;
2002
2003 case 4:
2004 rb->set_int("Zoom", "", 1, &zoom,
2005 NULL, 1, 10, 300, NULL );
2006 recalc_offsets();
2007 reset_slides();
2008 break;
2009 case 5:
2010 album_name_menu();
2011 reset_track_list();
2012 recalc_offsets();
2013 reset_slides();
2014 break;
2015 case 6:
2016 old_val = resize;
2017 rb->set_bool("Resize Covers", &resize);
2018 if (old_val == resize) /* changed? */
2019 break;
2020 /* fallthrough if changed, since cache needs to be rebuilt */
2021 case 7:
2022 cache_version = 0;
2023 rb->remove(EMPTY_SLIDE);
2024 rb->splash(HZ, "Cache will be rebuilt on next restart");
2025 break;
2026
2027 case MENU_ATTACHED_USB:
2028 return PLUGIN_USB_CONNECTED;
2029 }
2030 } while ( selection >= 0 );
2031 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2032 return 0;
2033}
2034
2035/**
2036 Show the main menu
2037 */
2038int main_menu(void)
2039{
2040 int selection = 0;
2041 int result;
2042
2043#if LCD_DEPTH > 1
2044 rb->lcd_set_foreground(N_BRIGHT(255));
2045#endif
2046
2047 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2048 "Settings", "Return", "Quit");
2049 while (1) {
2050 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2051 case 0:
2052 result = settings_menu();
2053 if ( result != 0 ) return result;
2054 break;
2055
2056 case 1:
2057 return 0;
2058
2059 case 2:
2060 return -1;
2061
2062 case MENU_ATTACHED_USB:
2063 return PLUGIN_USB_CONNECTED;
2064
2065 default:
2066 return 0;
2067 }
2068 }
2069}
2070
2071/**
2072 Animation step for zooming into the current cover
2073 */
2074void update_cover_in_animation(void)
2075{
2076 cover_animation_keyframe++;
2077 if( cover_animation_keyframe < 20 ) {
2078 center_slide.distance-=5;
2079 center_slide.angle+=1;
2080 extra_fade += 13;
2081 }
2082 else if( cover_animation_keyframe < 35 ) {
2083 center_slide.angle+=16;
2084 }
2085 else {
2086 cover_animation_keyframe = 0;
2087 pf_state = pf_show_tracks;
2088 }
2089}
2090
2091/**
2092 Animation step for zooming out the current cover
2093 */
2094void update_cover_out_animation(void)
2095{
2096 cover_animation_keyframe++;
2097 if( cover_animation_keyframe <= 15 ) {
2098 center_slide.angle-=16;
2099 }
2100 else if( cover_animation_keyframe < 35 ) {
2101 center_slide.distance+=5;
2102 center_slide.angle-=1;
2103 extra_fade -= 13;
2104 }
2105 else {
2106 cover_animation_keyframe = 0;
2107 pf_state = pf_idle;
2108 }
2109}
2110
2111/**
2112 Draw a blue gradient at y with height h
2113 */
2114static inline void draw_gradient(int y, int h)
2115{
2116 static int r, inc, c;
2117 inc = (100 << 8) / h;
2118 c = 0;
2119 selected_track_pulse = (selected_track_pulse+1) % 10;
2120 int c2 = selected_track_pulse - 5;
2121 for (r=0; r<h; r++) {
2122#ifdef HAVE_LCD_COLOR
2123 MYLCD(set_foreground)(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2124 c2+250-(c >> 8)));
2125#else
2126 MYLCD(set_foreground)(G_BRIGHT(c2+160-(c >> 8)));
2127#endif
2128 MYLCD(hline)(0, LCD_WIDTH, r+y);
2129 if ( r > h/2 )
2130 c-=inc;
2131 else
2132 c+=inc;
2133 }
2134}
2135
2136
2137static void track_list_yh(int char_height)
2138{
2139 switch (show_album_name)
2140 {
2141 case album_name_hide:
2142 track_list_y = (show_fps ? char_height : 0);
2143 track_list_h = LCD_HEIGHT - track_list_y;
2144 break;
2145 case album_name_bottom:
2146 track_list_y = (show_fps ? char_height : 0);
2147 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2148 break;
2149 default: /* case album_name_top */
2150 track_list_y = char_height * 2;
2151 track_list_h = LCD_HEIGHT - track_list_y -
2152 (show_fps ? char_height : 0);
2153 break;
2154 }
2155}
2156
2157/**
2158 Reset the track list after a album change
2159 */
2160void reset_track_list(void)
2161{
2162 int albumtxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2163 track_list_yh(albumtxt_h);
2164 track_list_visible_entries = fmin( track_list_h/albumtxt_h , track_count );
2165 start_index_track_list = 0;
2166 track_scroll_index = 0;
2167 track_scroll_dir = 1;
2168 selected_track = 0;
2169
2170 /* let the tracklist start more centered
2171 * if the screen isn't filled with tracks */
2172 if (track_count*albumtxt_h < track_list_h)
2173 {
2174 track_list_h = track_count * albumtxt_h;
2175 track_list_y = LCD_HEIGHT / 2 - (track_list_h / 2);
2176 }
2177}
2178
2179/**
2180 Display the list of tracks
2181 */
2182void show_track_list(void)
2183{
2184 MYLCD(clear_display)();
2185 if ( center_slide.slide_index != track_index ) {
2186 create_track_index(center_slide.slide_index);
2187 reset_track_list();
2188 }
2189 static int titletxt_w, titletxt_x, color, titletxt_h;
2190 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2191
2192 int titletxt_y = track_list_y;
2193 int track_i;
2194 track_i = start_index_track_list;
2195 for (;track_i < track_list_visible_entries+start_index_track_list;
2196 track_i++)
2197 {
2198 MYLCD(getstringsize)(get_track_name(track_i), &titletxt_w, NULL);
2199 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2200 if ( track_i == selected_track ) {
2201 draw_gradient(titletxt_y, titletxt_h);
2202 MYLCD(set_foreground)(G_BRIGHT(255));
2203 if (titletxt_w > LCD_WIDTH ) {
2204 if ( titletxt_w + track_scroll_index <= LCD_WIDTH )
2205 track_scroll_dir = 1;
2206 else if ( track_scroll_index >= 0 ) track_scroll_dir = -1;
2207 track_scroll_index += track_scroll_dir*2;
2208 titletxt_x = track_scroll_index;
2209 }
2210 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2211 }
2212 else {
2213 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2214 MYLCD(set_foreground)(G_BRIGHT(color));
2215 MYLCD(putsxy)(titletxt_x,titletxt_y,get_track_name(track_i));
2216 }
2217 titletxt_y += titletxt_h;
2218 }
2219}
2220
2221void select_next_track(void)
2222{
2223 if ( selected_track < track_count - 1 ) {
2224 selected_track++;
2225 track_scroll_index = 0;
2226 track_scroll_dir = 1;
2227 if (selected_track==(track_list_visible_entries+start_index_track_list))
2228 start_index_track_list++;
2229 }
2230}
2231
2232void select_prev_track(void)
2233{
2234 if (selected_track > 0 ) {
2235 if (selected_track==start_index_track_list) start_index_track_list--;
2236 track_scroll_index = 0;
2237 track_scroll_dir = 1;
2238 selected_track--;
2239 }
2240}
2241
2242/**
2243 Draw the current album name
2244 */
2245void draw_album_text(void)
2246{
2247 if (0 == show_album_name)
2248 return;
2249
2250 int albumtxt_w, albumtxt_h;
2251 int albumtxt_y = 0;
2252
2253 char *albumtxt;
2254 int c;
2255 /* Draw album text */
2256 if ( pf_state == pf_scrolling ) {
2257 c = ((slide_frame & 0xffff )/ 255);
2258 if (step < 0) c = 255-c;
2259 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2260 albumtxt = get_album_name(center_index+step);
2261 c = (c-128)*2;
2262 }
2263 else {
2264 albumtxt = get_album_name(center_index);
2265 c = (128-c)*2;
2266 }
2267 }
2268 else {
2269 c= 255;
2270 albumtxt = get_album_name(center_index);
2271 }
2272
2273 MYLCD(set_foreground)(G_BRIGHT(c));
2274 MYLCD(getstringsize)(albumtxt, &albumtxt_w, &albumtxt_h);
2275 if (center_index != prev_center_index) {
2276 albumtxt_x = 0;
2277 albumtxt_dir = -1;
2278 prev_center_index = center_index;
2279 }
2280
2281 if (show_album_name == album_name_top)
2282 albumtxt_y = albumtxt_h / 2;
2283 else
2284 albumtxt_y = LCD_HEIGHT - albumtxt_h - albumtxt_h/2;
2285
2286 if (albumtxt_w > LCD_WIDTH ) {
2287 MYLCD(putsxy)(albumtxt_x, albumtxt_y , albumtxt);
2288 if ( pf_state == pf_idle || pf_state == pf_show_tracks ) {
2289 if ( albumtxt_w + albumtxt_x <= LCD_WIDTH ) albumtxt_dir = 1;
2290 else if ( albumtxt_x >= 0 ) albumtxt_dir = -1;
2291 albumtxt_x += albumtxt_dir;
2292 }
2293 }
2294 else {
2295 MYLCD(putsxy)((LCD_WIDTH - albumtxt_w) /2, albumtxt_y , albumtxt);
2296 }
2297
2298
2299}
2300
2301
2302/**
2303 Main function that also contain the main plasma
2304 algorithm.
2305 */
2306int main(void)
2307{
2308 int ret;
2309
2310 rb->lcd_setfont(FONT_UI);
2311 draw_splashscreen();
2312
2313 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2314 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2315 rb->splash(HZ, "Could not create directory " CACHE_PREFIX );
2316 return PLUGIN_ERROR;
2317 }
2318 }
2319
2320 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2321
2322 init_reflect_table();
2323
2324 ALIGN_BUFFER(buf, buf_size, 4);
2325 ret = create_album_index();
2326 if (ret == ERROR_BUFFER_FULL) {
2327 rb->splash(HZ, "Not enough memory for album names");
2328 return PLUGIN_ERROR;
2329 } else if (ret == ERROR_NO_ALBUMS) {
2330 rb->splash(HZ, "No albums found. Please enable database");
2331 return PLUGIN_ERROR;
2332 }
2333
2334 ALIGN_BUFFER(buf, buf_size, 4);
2335 number_of_slides = album_count;
2336 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2337 rb->splash(HZ, "Could not create album art cache");
2338 return PLUGIN_ERROR;
2339 }
2340
2341 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2342 rb->splash(HZ, "Could not load the empty slide");
2343 return PLUGIN_ERROR;
2344 }
2345 cache_version = CACHE_VERSION;
2346 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2347
2348
2349#ifdef USEGSLIB
2350 long grey_buf_used;
2351 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2352 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2353 {
2354 rb->splash(HZ, "Greylib init failed!");
2355 return PLUGIN_ERROR;
2356 }
2357 grey_setfont(FONT_UI);
2358 buf_size -= grey_buf_used;
2359 buf = (void*)(grey_buf_used + (char*)buf);
2360#endif
2361 buflib_init(&buf_ctx, (void *)buf, buf_size);
2362
2363 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2364 {
2365 rb->splash(HZ, "Unable to load empty slide image");
2366 return PLUGIN_ERROR;
2367 }
2368
2369 if (!create_pf_thread()) {
2370 rb->splash(HZ, "Cannot create thread!");
2371 return PLUGIN_ERROR;
2372 }
2373
2374 int i;
2375
2376 /* initialize */
2377 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2378 cache[i].hid = 0;
2379 cache[i].index = 0;
2380 cache[i].next = i + 1;
2381 cache[i].prev = i - 1;
2382 }
2383 cache[0].prev = i - 1;
2384 cache[i - 1].next = 0;
2385 cache_free = 0;
2386 buffer = LCD_BUF;
2387
2388 pf_state = pf_idle;
2389
2390 track_index = -1;
2391 extra_fade = 0;
2392 slide_frame = 0;
2393 step = 0;
2394 target = 0;
2395 fade = 256;
2396
2397 recalc_offsets();
2398 reset_slides();
2399
2400 char fpstxt[10];
2401 int button;
2402
2403 int frames = 0;
2404 long last_update = *rb->current_tick;
2405 long current_update;
2406 long update_interval = 100;
2407 int fps = 0;
2408 int fpstxt_y;
2409
2410 bool instant_update;
2411#ifdef USEGSLIB
2412 grey_show(true);
2413 grey_set_drawmode(DRMODE_FG);
2414#endif
2415 rb->lcd_set_drawmode(DRMODE_FG);
2416 while (true) {
2417 current_update = *rb->current_tick;
2418 frames++;
2419
2420 /* Initial rendering */
2421 instant_update = false;
2422
2423 /* Handle states */
2424 switch ( pf_state ) {
2425 case pf_scrolling:
2426 update_scroll_animation();
2427 render_all_slides();
2428 instant_update = true;
2429 break;
2430 case pf_cover_in:
2431 update_cover_in_animation();
2432 render_all_slides();
2433 instant_update = true;
2434 break;
2435 case pf_cover_out:
2436 update_cover_out_animation();
2437 render_all_slides();
2438 instant_update = true;
2439 break;
2440 case pf_show_tracks:
2441 show_track_list();
2442 break;
2443 case pf_idle:
2444 render_all_slides();
2445 break;
2446 }
2447
2448 /* Calculate FPS */
2449 if (current_update - last_update > update_interval) {
2450 fps = frames * HZ / (current_update - last_update);
2451 last_update = current_update;
2452 frames = 0;
2453 }
2454 /* Draw FPS */
2455 if (show_fps)
2456 {
2457#ifdef USEGSLIB
2458 MYLCD(set_foreground)(G_BRIGHT(255));
2459#else
2460 MYLCD(set_foreground)(G_PIX(255,0,0));
2461#endif
2462 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2463 if (show_album_name == album_name_top)
2464 fpstxt_y = LCD_HEIGHT -
2465 rb->screens[SCREEN_MAIN]->getcharheight();
2466 else
2467 fpstxt_y = 0;
2468 MYLCD(putsxy)(0, fpstxt_y, fpstxt);
2469 }
2470 draw_album_text();
2471
2472
2473 /* Copy offscreen buffer to LCD and give time to other threads */
2474 MYLCD(update)();
2475 rb->yield();
2476
2477 /*/ Handle buttons */
2478 button = rb->get_custom_action(CONTEXT_CUSTOM|
2479 (pf_state == pf_show_tracks ? 1 : 0),
2480 instant_update ? 0 : HZ/16,
2481 get_context_map);
2482
2483 switch (button) {
2484 case PF_QUIT:
2485 return PLUGIN_OK;
2486
2487 case PF_BACK:
2488 if ( pf_state == pf_show_tracks )
2489 pf_state = pf_cover_out;
2490 if (pf_state == pf_idle || pf_state == pf_scrolling)
2491 return PLUGIN_OK;
2492 break;
2493
2494 case PF_MENU:
2495#ifdef USEGSLIB
2496 grey_show(false);
2497#endif
2498 ret = main_menu();
2499 if ( ret == -1 ) return PLUGIN_OK;
2500 if ( ret != 0 ) return i;
2501#ifdef USEGSLIB
2502 grey_show(true);
2503#endif
2504 MYLCD(set_drawmode)(DRMODE_FG);
2505 break;
2506
2507 case PF_NEXT:
2508 case PF_NEXT_REPEAT:
2509 if ( pf_state == pf_show_tracks )
2510 select_next_track();
2511 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2512 show_next_slide();
2513 break;
2514
2515 case PF_PREV:
2516 case PF_PREV_REPEAT:
2517 if ( pf_state == pf_show_tracks )
2518 select_prev_track();
2519 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2520 show_previous_slide();
2521 break;
2522
2523 case PF_SELECT:
2524 if ( pf_state == pf_idle ) {
2525 pf_state = pf_cover_in;
2526 }
2527 break;
2528
2529 default:
2530 if (rb->default_event_handler_ex(button, cleanup, NULL)
2531 == SYS_USB_CONNECTED)
2532 return PLUGIN_USB_CONNECTED;
2533 break;
2534 }
2535 }
2536
2537
2538}
2539
2540/*************************** Plugin entry point ****************************/
2541
2542enum plugin_status plugin_start(const void *parameter)
2543{
2544 int ret;
2545 (void) parameter;
2546#if LCD_DEPTH > 1
2547 rb->lcd_set_backdrop(NULL);
2548#endif
2549 /* Turn off backlight timeout */
2550 backlight_force_on(); /* backlight control in lib/helper.c */
2551#ifdef HAVE_ADJUSTABLE_CPU_FREQ
2552 rb->cpu_boost(true);
2553#endif
2554#if PLUGIN_BUFFER_SIZE > 0x10000 && 0
2555 buf = rb->plugin_get_buffer(&buf_size);
2556#else
2557 buf = rb->plugin_get_audio_buffer(&buf_size);
2558#endif
2559 ret = main();
2560#ifdef HAVE_ADJUSTABLE_CPU_FREQ
2561 rb->cpu_boost(false);
2562#endif
2563 if ( ret == PLUGIN_OK ) {
2564 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2565 CONFIG_VERSION))
2566 {
2567 rb->splash(HZ, "Error writing config.");
2568 ret = PLUGIN_ERROR;
2569 }
2570 }
2571
2572 end_pf_thread();
2573 cleanup(NULL);
2574 return ret;
2575}