diff --git a/apps/plugin.c b/apps/plugin.c index 9067097..9b3e679 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -532,6 +532,24 @@ static const struct plugin_api rockbox_api = { remote_backlight_set_timeout_plugged, #endif #endif /* HAVE_REMOTE_LCD */ + +#if (CONFIG_CODEC == SWCODEC) + bufopen, + bufalloc, + bufclose, + bufseek, + bufadvance, + bufread, + bufgetdata, + bufgettail, + bufcuttail, + + buf_get_offset, + buf_handle_offset, + buf_request_buffer_handle, + buf_set_base_handle, + buf_used, +#endif }; int plugin_load(const char* plugin, void* parameter) diff --git a/apps/plugin.h b/apps/plugin.h index 9105932..825b0a0 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -78,6 +78,7 @@ #include "list.h" #include "tree.h" #include "color_picker.h" +#include "buffering.h" #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" @@ -655,6 +656,24 @@ struct plugin_api { void (*remote_backlight_set_timeout_plugged)(int index); #endif #endif /* HAVE_REMOTE_LCD */ + +#if (CONFIG_CODEC == SWCODEC) + int (*bufopen)(const char *file, size_t offset, enum data_type type); + int (*bufalloc)(const void *src, size_t size, enum data_type type); + bool (*bufclose)(int handle_id); + int (*bufseek)(int handle_id, size_t newpos); + int (*bufadvance)(int handle_id, off_t offset); + ssize_t (*bufread)(int handle_id, size_t size, void *dest); + ssize_t (*bufgetdata)(int handle_id, size_t size, void **data); + ssize_t (*bufgettail)(int handle_id, size_t size, void **data); + ssize_t (*bufcuttail)(int handle_id, size_t size); + + ssize_t (*buf_get_offset)(int handle_id, void *ptr); + ssize_t (*buf_handle_offset)(int handle_id); + void (*buf_request_buffer_handle)(int handle_id); + void (*buf_set_base_handle)(int handle_id); + size_t (*buf_used)(void); +#endif }; /* plugin header */ diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 709c5be..57cab53 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -47,6 +47,7 @@ mpegplayer,viewers nim,games oscilloscope,demos pacbox,games +pictureflow,demos plasma,demos pong,games properties,viewers diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 727e5a8..b24fef1 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -20,6 +20,8 @@ stopwatch.c vbrfix.c viewer.c +pictureflow.c + #ifdef OLYMPUS_MROBE_500 /* remove these once the plugins before it are compileable */ jpeg.c diff --git a/apps/plugins/pictureflow.c b/apps/plugins/pictureflow.c new file mode 100644 index 0000000..aeb34bd --- /dev/null +++ b/apps/plugins/pictureflow.c @@ -0,0 +1,1215 @@ + +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* +* Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st) +* Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version) +* +* PictureFlow Demo +* +* All files in this archive are subject to the GNU General Public License. +* See the file COPYING in the source tree root for full license agreement. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +****************************************************************************/ + +#include "plugin.h" +#include "helper.h" +#include "lib/bmp.h" + + +#ifdef HAVE_LCD_BITMAP /* and also not for the Player */ + +PLUGIN_HEADER +/******************************* Globals ***********************************/ +static struct plugin_api *rb; /* global api struct pointer */ + +/* Key assignement */ + +#if (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define PICTUREFLOW_QUIT BUTTON_MENU +#define PICTUREFLOW_SELECT_ALBUM BUTTON_SELECT +#define PICTUREFLOW_NEXT_ALBUM BUTTON_SCROLL_FWD +#define PICTUREFLOW_PREV_ALBUM BUTTON_SCROLL_BACK +#endif + + +#if CONFIG_KEYPAD == GIGABEAT_PAD +#define PICTUREFLOW_QUIT BUTTON_MENU +#define PICTUREFLOW_SELECT_ALBUM BUTTON_SELECT +#define PICTUREFLOW_NEXT_ALBUM BUTTON_RIGHT +#define PICTUREFLOW_PREV_ALBUM BUTTON_LEFT +#endif + + + + +// for fixed-point arithmetic, we need minimum 32-bit long +// long long (64-bit) might be useful for multiplication and division +#define PFreal long +#define PFREAL_SHIFT 10 +#define PFREAL_FACTOR (1 << PFREAL_SHIFT) +#define PFREAL_ONE (1 << PFREAL_SHIFT) +#define PFREAL_HALF (PFREAL_ONE >> 1) + + +#define fmin(a,b) (((a) < (b)) ? (a) : (b)) +#define fmax(a,b) (((a) > (b)) ? (a) : (b)) +#define fbound(min,val,max) (fmax((min),fmin((max),(val)))) + +// There are some precision issues when not using (long long) which in turn takes very long to compute... +// I guess the best solution would be to optimize the computations so it only requires a single long + +static inline PFreal fmul(PFreal a, PFreal b) +{ + return ((long long) (a)) * ((long long) (b)) >> PFREAL_SHIFT; +} + +static inline PFreal fdiv(PFreal num, PFreal den) +{ + long long p = (long long) (num) << (PFREAL_SHIFT * 2); + long long q = p / (long long) den; + long long r = q >> PFREAL_SHIFT; + + return r; +} + +/* +#define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT ) +#define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m ) + +#define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2))) +#define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) ) +*/ + +/* +static inline PFreal fmul(PFreal a, PFreal b) +{ + return (a*b) >> PFREAL_SHIFT; +} + +static inline PFreal fdiv(PFreal n, PFreal m) +{ + return (n<<(PFREAL_SHIFT))/m; +} +*/ + + +#define IANGLE_MAX 1024 +#define IANGLE_MASK 1023 + +/** + Precomupted sin-table +*/ +static const PFreal sinTable[IANGLE_MAX] = { // 10 + 3, 9, 15, 21, 28, 34, 40, 47, + 53, 59, 65, 72, 78, 84, 90, 97, + 103, 109, 115, 122, 128, 134, 140, 147, + 153, 159, 165, 171, 178, 184, 190, 196, + 202, 209, 215, 221, 227, 233, 239, 245, + 251, 257, 264, 270, 276, 282, 288, 294, + 300, 306, 312, 318, 324, 330, 336, 342, + 347, 353, 359, 365, 371, 377, 383, 388, + 394, 400, 406, 412, 417, 423, 429, 434, + 440, 446, 451, 457, 463, 468, 474, 479, + 485, 491, 496, 501, 507, 512, 518, 523, + 529, 534, 539, 545, 550, 555, 561, 566, + 571, 576, 581, 587, 592, 597, 602, 607, + 612, 617, 622, 627, 632, 637, 642, 647, + 652, 656, 661, 666, 671, 675, 680, 685, + 690, 694, 699, 703, 708, 712, 717, 721, + 726, 730, 735, 739, 743, 748, 752, 756, + 760, 765, 769, 773, 777, 781, 785, 789, + 793, 797, 801, 805, 809, 813, 816, 820, + 824, 828, 831, 835, 839, 842, 846, 849, + 853, 856, 860, 863, 866, 870, 873, 876, + 879, 883, 886, 889, 892, 895, 898, 901, + 904, 907, 910, 913, 916, 918, 921, 924, + 927, 929, 932, 934, 937, 939, 942, 944, + 947, 949, 951, 954, 956, 958, 960, 963, + 965, 967, 969, 971, 973, 975, 977, 978, + 980, 982, 984, 986, 987, 989, 990, 992, + 994, 995, 997, 998, 999, 1001, 1002, 1003, + 1004, 1006, 1007, 1008, 1009, 1010, 1011, 1012, + 1013, 1014, 1015, 1015, 1016, 1017, 1018, 1018, + 1019, 1019, 1020, 1020, 1021, 1021, 1022, 1022, + 1022, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1022, + 1022, 1022, 1021, 1021, 1020, 1020, 1019, 1019, + 1018, 1018, 1017, 1016, 1015, 1015, 1014, 1013, + 1012, 1011, 1010, 1009, 1008, 1007, 1006, 1004, + 1003, 1002, 1001, 999, 998, 997, 995, 994, + 992, 990, 989, 987, 986, 984, 982, 980, + 978, 977, 975, 973, 971, 969, 967, 965, + 963, 960, 958, 956, 954, 951, 949, 947, + 944, 942, 939, 937, 934, 932, 929, 927, + 924, 921, 918, 916, 913, 910, 907, 904, + 901, 898, 895, 892, 889, 886, 883, 879, + 876, 873, 870, 866, 863, 860, 856, 853, + 849, 846, 842, 839, 835, 831, 828, 824, + 820, 816, 813, 809, 805, 801, 797, 793, + 789, 785, 781, 777, 773, 769, 765, 760, + 756, 752, 748, 743, 739, 735, 730, 726, + 721, 717, 712, 708, 703, 699, 694, 690, + 685, 680, 675, 671, 666, 661, 656, 652, + 647, 642, 637, 632, 627, 622, 617, 612, + 607, 602, 597, 592, 587, 581, 576, 571, + 566, 561, 555, 550, 545, 539, 534, 529, + 523, 518, 512, 507, 501, 496, 491, 485, + 479, 474, 468, 463, 457, 451, 446, 440, + 434, 429, 423, 417, 412, 406, 400, 394, + 388, 383, 377, 371, 365, 359, 353, 347, + 342, 336, 330, 324, 318, 312, 306, 300, + 294, 288, 282, 276, 270, 264, 257, 251, + 245, 239, 233, 227, 221, 215, 209, 202, + 196, 190, 184, 178, 171, 165, 159, 153, + 147, 140, 134, 128, 122, 115, 109, 103, + 97, 90, 84, 78, 72, 65, 59, 53, + 47, 40, 34, 28, 21, 15, 9, 3, + -4, -10, -16, -22, -29, -35, -41, -48, + -54, -60, -66, -73, -79, -85, -91, -98, + -104, -110, -116, -123, -129, -135, -141, -148, + -154, -160, -166, -172, -179, -185, -191, -197, + -203, -210, -216, -222, -228, -234, -240, -246, + -252, -258, -265, -271, -277, -283, -289, -295, + -301, -307, -313, -319, -325, -331, -337, -343, + -348, -354, -360, -366, -372, -378, -384, -389, + -395, -401, -407, -413, -418, -424, -430, -435, + -441, -447, -452, -458, -464, -469, -475, -480, + -486, -492, -497, -502, -508, -513, -519, -524, + -530, -535, -540, -546, -551, -556, -562, -567, + -572, -577, -582, -588, -593, -598, -603, -608, + -613, -618, -623, -628, -633, -638, -643, -648, + -653, -657, -662, -667, -672, -676, -681, -686, + -691, -695, -700, -704, -709, -713, -718, -722, + -727, -731, -736, -740, -744, -749, -753, -757, + -761, -766, -770, -774, -778, -782, -786, -790, + -794, -798, -802, -806, -810, -814, -817, -821, + -825, -829, -832, -836, -840, -843, -847, -850, + -854, -857, -861, -864, -867, -871, -874, -877, + -880, -884, -887, -890, -893, -896, -899, -902, + -905, -908, -911, -914, -917, -919, -922, -925, + -928, -930, -933, -935, -938, -940, -943, -945, + -948, -950, -952, -955, -957, -959, -961, -964, + -966, -968, -970, -972, -974, -976, -978, -979, + -981, -983, -985, -987, -988, -990, -991, -993, + -995, -996, -998, -999, -1000, -1002, -1003, -1004, + -1005, -1007, -1008, -1009, -1010, -1011, -1012, -1013, + -1014, -1015, -1016, -1016, -1017, -1018, -1019, -1019, + -1020, -1020, -1021, -1021, -1022, -1022, -1023, -1023, + -1023, -1024, -1024, -1024, -1024, -1024, -1024, -1024, + -1024, -1024, -1024, -1024, -1024, -1024, -1024, -1023, + -1023, -1023, -1022, -1022, -1021, -1021, -1020, -1020, + -1019, -1019, -1018, -1017, -1016, -1016, -1015, -1014, + -1013, -1012, -1011, -1010, -1009, -1008, -1007, -1005, + -1004, -1003, -1002, -1000, -999, -998, -996, -995, + -993, -991, -990, -988, -987, -985, -983, -981, + -979, -978, -976, -974, -972, -970, -968, -966, + -964, -961, -959, -957, -955, -952, -950, -948, + -945, -943, -940, -938, -935, -933, -930, -928, + -925, -922, -919, -917, -914, -911, -908, -905, + -902, -899, -896, -893, -890, -887, -884, -880, + -877, -874, -871, -867, -864, -861, -857, -854, + -850, -847, -843, -840, -836, -832, -829, -825, + -821, -817, -814, -810, -806, -802, -798, -794, + -790, -786, -782, -778, -774, -770, -766, -761, + -757, -753, -749, -744, -740, -736, -731, -727, + -722, -718, -713, -709, -704, -700, -695, -691, + -686, -681, -676, -672, -667, -662, -657, -653, + -648, -643, -638, -633, -628, -623, -618, -613, + -608, -603, -598, -593, -588, -582, -577, -572, + -567, -562, -556, -551, -546, -540, -535, -530, + -524, -519, -513, -508, -502, -497, -492, -486, + -480, -475, -469, -464, -458, -452, -447, -441, + -435, -430, -424, -418, -413, -407, -401, -395, + -389, -384, -378, -372, -366, -360, -354, -348, + -343, -337, -331, -325, -319, -313, -307, -301, + -295, -289, -283, -277, -271, -265, -258, -252, + -246, -240, -234, -228, -222, -216, -210, -203, + -197, -191, -185, -179, -172, -166, -160, -154, + -148, -141, -135, -129, -123, -116, -110, -104, + -98, -91, -85, -79, -73, -66, -60, -54, + -48, -41, -35, -29, -22, -16, -10, -4 +}; + +inline PFreal fsin(int iangle) +{ + while (iangle < 0) + iangle += IANGLE_MAX; + return sinTable[iangle & IANGLE_MASK]; +} + +inline PFreal fcos(int iangle) +{ + // quarter phase shift + return fsin(iangle + (IANGLE_MAX >> 2)); +} + + +#define IMG_WIDTH 100 +#define IMG_HEIGHT 100 +#define SLIDE_WIDTH 100 +#define SLIDE_HEIGHT 100 +#define BUFFER_WIDTH LCD_WIDTH +#define BUFFER_HEIGHT LCD_HEIGHT + +// TODO: it would be nice if we could actually make use of much more memory +// this would speed up the whole process but we hit the PLUGIN_RAM (512k) at about +// 11 (where one slide is 100*200*2 ~ 40kb). +// It seems we need to do our own malloc and grab memory from the audio buffer. +// Given the 40kb per slide, we would get 500 slides into 20MB. +// The question is, how much audio buffer we can allocate until we have a noticable +// negative effect. +#define SLIDE_CACHE_SIZE 11 +#define LEFT_SLIDES_COUNT 3 +#define RIGHT_SLIDES_COUNT 3 + + +#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200 + + + +struct slide_data { + int slide_index; + int angle; + PFreal cx; + PFreal cy; +}; + + +struct rect { + int left; + int right; + int top; + int bottom; +}; + +struct load_slide_event_data { + int slide_index; + int cache_index; +}; + +/** below we allocate the memory we want to use **/ + +static fb_data *buffer; +static PFreal rays[BUFFER_WIDTH]; +static bool animation_is_active; // an animation is currently running +static struct slide_data center_slide; +static struct slide_data left_slides[LEFT_SLIDES_COUNT]; +static struct slide_data right_slides[RIGHT_SLIDES_COUNT]; +static int slide_frame; +static int step; +static int target; +static int fade; +static int center_index; // index of the slide that is in the center +static int itilt; +static int spacing; // spacing between slides +static int zoom; +static PFreal offsetX; +static PFreal offsetY; +static bool show_fps; // show fps in the main screen +static int number_of_slides; + + +static int slide_cache_map[SLIDE_CACHE_SIZE+1]; // map cached slides to an index +static int slide_cache_hid[SLIDE_CACHE_SIZE+1]; // map cached slides to a handle ID +static long slide_cache_touched[SLIDE_CACHE_SIZE+1]; // map cached slides to an index +static int slide_cache_in_use; + +/* use long for aligning */ +unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)]; +static int slide_cache_stack[SLIDE_CACHE_SIZE]; // queue (as array) for scheduling load_surface +static int slide_cache_stack_index; +struct mutex slide_cache_stack_lock; + +struct thread_entry *thread_id; +struct event_queue thread_q; + +#define EV_EXIT 9999 +#define EV_WAKEUP 1337 + +int load_surface(int); + +/** + Return the index on the stack of slide_index. + Return -1 if slide_index is not on the stack. + */ +int slide_stack_get_index(int slide_index) +{ + int i = slide_cache_stack_index + 1; + while (i--) { + if ( slide_cache_stack[i] == slide_index ) return i; + }; + return -1; +} + +/** + Push the slide_index on the stack so the image will be loaded. + The algorithm tries to keep the center_index on top and the + slide_index as high as possible (so second if center_index is + on the stack). + */ +void slide_stack_push(int slide_index) +{ + rb->mutex_lock(&slide_cache_stack_lock); + + if ( slide_cache_stack_index == -1 ) { + // empty stack, no checks at all + slide_cache_stack[ ++slide_cache_stack_index ] = slide_index; + rb->mutex_unlock(&slide_cache_stack_lock); + return; + } + + int i = slide_stack_get_index( slide_index ); + + if ( i == slide_cache_stack_index ) { + // slide_index is on top, so we do not change anything + rb->mutex_unlock(&slide_cache_stack_lock); + return; + } + + if ( i >= 0 ) { + // slide_index is already on the stack, but not on top + int tmp = slide_cache_stack[ slide_cache_stack_index ]; + if ( tmp == center_index ) { + // the center_index is on top of the stack so do not touch that + if ( slide_cache_stack_index > 0 ) { + // but maybe it is possible to swap the given slide_index to the second place + tmp = slide_cache_stack[ slide_cache_stack_index -1 ]; + slide_cache_stack[ slide_cache_stack_index - 1 ] = slide_cache_stack[ i ]; + slide_cache_stack[ i ] = tmp; + } + } + else { + // if the center_index is not on top (i.e. already loaded) bring the slide_index to the top + slide_cache_stack[ slide_cache_stack_index ] = slide_cache_stack[ i ]; + slide_cache_stack[ i ] = tmp; + } + } + else { + // slide_index is not on the stack + if ( slide_cache_stack_index >= SLIDE_CACHE_SIZE-1 ) { + // if we exceeded the stack size, clear the first half of the stack + slide_cache_stack_index = SLIDE_CACHE_SIZE/2; + for (i = 0; i <= slide_cache_stack_index ; i++) + slide_cache_stack[ i ] = slide_cache_stack[ i + slide_cache_stack_index ]; + } + if ( slide_cache_stack[ slide_cache_stack_index ] == center_index ) { + // if the center_index is on top leave it there + slide_cache_stack[ slide_cache_stack_index ] = slide_index; + slide_cache_stack[ ++slide_cache_stack_index ] = center_index; + } + else { + // usual stack case: push the slide_index on top + slide_cache_stack[ ++slide_cache_stack_index ] = slide_index; + } + } + rb->mutex_unlock(&slide_cache_stack_lock); +} + +/** + Pop the topmost item from the stack and decrease the stack size + */ +inline int slide_stack_pop(void) +{ + rb->mutex_lock(&slide_cache_stack_lock); + int result; + if ( slide_cache_stack_index >= 0 ) + result = slide_cache_stack[ slide_cache_stack_index-- ]; + else + result = -1; + rb->mutex_unlock(&slide_cache_stack_lock); + return result; +} + +/** + Load the slide into the cache. + Thus we have to queue the loading request in our thread while discarding the oldest slide. + */ +void request_surface(int slide_index) +{ + slide_stack_push(slide_index); + rb->queue_post(&thread_q, EV_WAKEUP, 0); +} + + +/** + Thread used for loading and preparing bitmaps in the background + */ +void thread(void) +{ + long sleep_time = 5 * HZ; + struct queue_event ev; + while (1) { + rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time); + switch (ev.id) { + case EV_EXIT: + return; + case EV_WAKEUP: + // we just woke up + break; + } + int slide_index; + while ( (slide_index = slide_stack_pop()) != -1 ) { + load_surface( slide_index ); + rb->sleep( HZ ); // FIXME: optimize if we are satisified :) + } + } +} + + +/** + End the thread by posting the EV_EXIT event + */ +void end_pf_thread(void) +{ + rb->queue_post(&thread_q, EV_EXIT, 0); + rb->thread_wait(thread_id); + /* remove the thread's queue from the broadcast list */ + rb->queue_delete(&thread_q); +} + + +/** + Create the thread an setup the event queue + */ +bool create_pf_thread(void) +{ + rb->queue_init(&thread_q, true); /* put the thread's queue in the bcast list */ + if ((thread_id = rb->create_thread( + thread, + thread_stack, + sizeof(thread_stack), + 0, + "Picture load thread" + IF_PRIO(, PRIORITY_BACKGROUND) + IF_COP(, CPU) + ) + ) == NULL) { + return false; + } + return true; +} + + +static fb_data tmp_bmp_buffer[IMG_WIDTH * IMG_HEIGHT]; // static buffer for reading the bitmaps + +/** + Read the slide at the given index into the cache + TODO: This could be *much* more efficient + It probably would make sense to store the files in raw format + We would save the expensive 24->16bpp conversion and the computation of the + reflection. + */ +static bool read_bmp(int slide_index, int cache_index) +{ + struct bitmap tmp_bmp; + int ret; + tmp_bmp.data = (char *) &tmp_bmp_buffer; + char path[MAX_PATH]; + // TODO: map slide_index to the actual album filename + rb->snprintf(path, sizeof(path), "/pics/cover%d.bmp", slide_index+1); + ret = rb->read_bmp_file(path, &tmp_bmp, sizeof(tmp_bmp_buffer), FORMAT_NATIVE); + + int hid = rb->bufalloc(NULL, sizeof(struct bitmap) + (tmp_bmp.width+1) * tmp_bmp.height*2 * sizeof(fb_data), TYPE_BITMAP); + if (hid < 0) + return false; + + struct bitmap *bmp; + rb->bufgetdata(hid, 0, (void *)&bmp); + bmp->width = tmp_bmp.width; + bmp->height = tmp_bmp.height; + bmp->format = tmp_bmp.format; + bmp->data = (char *)(bmp + sizeof(struct bitmap)); + slide_cache_hid[cache_index] = hid; + + if (ret > 0) { + int h = bmp->height; + int w = bmp->width; + fb_data *result = (fb_data *)bmp->data; + // slightly larger, to accomodate for the reflection + int hs = h * 2; + int hofs = w / 3; + rb->memset(result, 0, sizeof(fb_data) * w * hs); + int x, y; + // transpose the image, this is to speed-up the rendering + // because we process one column at a time + // (and much better and faster to work row-wise, i.e in one scanline) + for (x = 0; x < w; x++) + for (y = 0; y < h; y++) + result[bmp->width * 2 * x + (hofs + y)] = + tmp_bmp_buffer[y * bmp->width + x]; + + // create the reflection + int ht = hs - h - hofs; + int hte = ht; + for (x = 0; x < w; x++) { + for (y = 0; y < ht; y++) { + fb_data color = tmp_bmp_buffer[x + bmp->width * (bmp->height - y - 1)]; + int r = RGB_UNPACK_RED(color) * (hte - y) / hte * 3 / 5; + int g = RGB_UNPACK_GREEN(color) * (hte - y) / hte * 3 / 5; + int b = RGB_UNPACK_BLUE(color) * (hte - y) / hte * 3 / 5; + result[h + hofs + y + bmp->width * x * 2] = + LCD_RGBPACK(r, g, b); + } + } + if ( cache_index < SLIDE_CACHE_SIZE ) { + slide_cache_map[cache_index] = slide_index; + slide_cache_touched[cache_index] = *rb->current_tick; + } + return true; + } + return false; +} + + + +/** + Load the surface from a bmp and store overwrite the oldest slide in the cache + */ +int load_surface(int slide_index) +{ + long oldest_tick = *rb->current_tick; + int oldest_slide = 0; + int i; + if ( slide_cache_in_use < SLIDE_CACHE_SIZE ) { // initial fill + oldest_slide = slide_cache_in_use; + read_bmp(slide_index, slide_cache_in_use++); + } + else { + for (i = 0; i < SLIDE_CACHE_SIZE; i++) { // look for oldest slide + if (slide_cache_touched[i] < oldest_tick) { + oldest_slide = i; + oldest_tick = slide_cache_touched[i]; + } + } + slide_cache_hid[oldest_slide] = -1; + rb->bufclose(slide_cache_hid[oldest_slide]); + read_bmp(slide_index, oldest_slide); + } + return oldest_slide; +} + + +/** + Get a slide from the buffer + */ +struct bitmap *get_slide(int hid) +{ + if (hid < 0) + return NULL; + + struct bitmap *bmp; + + ssize_t ret = rb->bufgetdata(hid, 0, (void *)&bmp); + if (ret < 0) + return NULL; + + return bmp; +} + + +/** + Return the requested surface +*/ +struct bitmap *surface(int slide_index) +{ + if (slide_index < 0) + return 0; + if (slide_index >= number_of_slides) + return 0; + + int i; + for (i = 0; i < slide_cache_in_use; i++) { // maybe do the inverse mapping => implies dynamic allocation? + if ( slide_cache_map[i] == slide_index ) { + // We have already loaded our slide, so touch it and return it. + slide_cache_touched[i] = *rb->current_tick; + return get_slide(slide_cache_hid[i]); + } + } + request_surface(slide_index); + return get_slide(slide_cache_hid[SLIDE_CACHE_SIZE]); +} + +/** + adjust slides so that they are in "steady state" position + */ +void reset_slides(void) +{ + center_slide.angle = 0; + center_slide.cx = 0; + center_slide.cy = 0; + center_slide.slide_index = center_index; + + int i; + for (i = 0; i < LEFT_SLIDES_COUNT; i++) { + struct slide_data *si = &left_slides[i]; + si->angle = itilt; + si->cx = -(offsetX + spacing * i * PFREAL_ONE); + si->cy = offsetY; + si->slide_index = center_index - 1 - i; + } + + for (i = 0; i < RIGHT_SLIDES_COUNT; i++) { + struct slide_data *si = &right_slides[i]; + si->angle = -itilt; + si->cx = offsetX + spacing * i * PFREAL_ONE; + si->cy = offsetY; + si->slide_index = center_index + 1 + i; + } +} + + +/** + Updates look-up table and other stuff necessary for the rendering. + Call this when the viewport size or slide dimension is changed. + */ +void recalc_table(void) +{ + int w = (BUFFER_WIDTH + 1) / 2; + int h = (BUFFER_HEIGHT + 1) / 2; + int i; + for (i = 0; i < w; i++) { + PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2 * h); + rays[w - i - 1] = -gg; + rays[w + i] = gg; + } + + + itilt = 70 * IANGLE_MAX / 360; // approx. 70 degrees tilted + + offsetX = SLIDE_WIDTH / 2 * (PFREAL_ONE - fcos(itilt)); + offsetY = SLIDE_WIDTH / 2 * fsin(itilt); + offsetX += SLIDE_WIDTH * PFREAL_ONE; + offsetY += SLIDE_WIDTH * PFREAL_ONE / 4; + spacing = 40; +} + + + +/** + render a single slide +*/ +void render_slide(struct slide_data *slide, struct rect *result_rect, + int alpha, int col1, int col2) +{ + rb->memset(result_rect, 0, sizeof(struct rect)); + struct bitmap *bmp = surface(slide->slide_index); + if (!bmp) { + return; + } + fb_data *src = (fb_data *)bmp->data; + + int sw = bmp->height; + int sh = bmp->width * 2; + + int h = LCD_HEIGHT; + int w = LCD_WIDTH; + + if (col1 > col2) { + int c = col2; + col2 = col1; + col1 = c; + } + + col1 = (col1 >= 0) ? col1 : 0; + col2 = (col2 >= 0) ? col2 : w - 1; + col1 = fmin(col1, w - 1); + col2 = fmin(col2, w - 1); + + int distance = h * 100 / zoom; + PFreal sdx = fcos(slide->angle); + PFreal sdy = fsin(slide->angle); + PFreal xs = slide->cx - bmp->width * sdx / 2; + PFreal ys = slide->cy - bmp->width * sdy / 2; + PFreal dist = distance * PFREAL_ONE; + + + int xi = fmax((PFreal) 0, + ((w * PFREAL_ONE / 2) + + fdiv(xs * h, dist + ys)) >> PFREAL_SHIFT); + if (xi >= w) { + return; + } + + bool flag = false; + result_rect->left = xi; + int x; + for (x = fmax(xi, col1); x <= col2; x++) { + PFreal hity = 0; + PFreal fk = rays[x]; + if (sdy) { + fk = fk - fdiv(sdx, sdy); + hity = -fdiv(( rays[x] * distance + - slide->cx + + slide->cy * sdx / sdy), fk); + } + + dist = distance * PFREAL_ONE + hity; + if (dist < 0) + continue; + + PFreal hitx = fmul(dist, rays[x]); + + PFreal hitdist = fdiv(hitx - slide->cx, sdx); + + int column = sw / 2 + (hitdist >> PFREAL_SHIFT); + if (column >= sw) + break; + + if (column < 0) + continue; + + result_rect->right = x; + if (!flag) + result_rect->left = x; + flag = true; + + int y1 = h / 2; + int y2 = y1 + 1; + fb_data *pixel1 = &buffer[y1 * BUFFER_WIDTH + x]; + fb_data *pixel2 = &buffer[y2 * BUFFER_WIDTH + x]; + int pixelstep = pixel2 - pixel1; + + int center = (sh / 2); + int dy = dist / h; + int p1 = center * PFREAL_ONE - dy / 2; + int p2 = center * PFREAL_ONE + dy / 2; + + + const fb_data *ptr = &src[column * bmp->width * 2]; + if (alpha == 256) + while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) { + *pixel1 = ptr[p1 >> PFREAL_SHIFT]; + *pixel2 = ptr[p2 >> PFREAL_SHIFT]; + p1 -= dy; + p2 += dy; + y1--; + y2++; + pixel1 -= pixelstep; + pixel2 += pixelstep; + } else + while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) { + fb_data c1 = ptr[p1 >> PFREAL_SHIFT]; + fb_data c2 = ptr[p2 >> PFREAL_SHIFT]; + + int r1 = RGB_UNPACK_RED(c1) * alpha / 256; + int g1 = RGB_UNPACK_GREEN(c1) * alpha / 256; + int b1 = RGB_UNPACK_BLUE(c1) * alpha / 256; + int r2 = RGB_UNPACK_RED(c2) * alpha / 256; + int g2 = RGB_UNPACK_GREEN(c2) * alpha / 256; + int b2 = RGB_UNPACK_BLUE(c2) * alpha / 256; + + *pixel1 = LCD_RGBPACK(r1, g1, b1); + *pixel2 = LCD_RGBPACK(r2, g2, b2); + p1 -= dy; + p2 += dy; + y1--; + y2++; + pixel1 -= pixelstep; + pixel2 += pixelstep; + } + } + // let the music play... + rb->yield(); + + result_rect->top = 0; + result_rect->bottom = h - 1; + return; +} + + +static inline void set_current_slide(int index) +{ + step = 0; + center_index = fbound(index, 0, number_of_slides - 1); + target = center_index; + slide_frame = index << 16; + reset_slides(); +} + +void start_animation(void) +{ + if (!animation_is_active) { + step = (target < center_slide.slide_index) ? -1 : 1; + animation_is_active = true; + } +} + +void show_previous_slide(void) +{ + if (step >= 0) { + if (center_index > 0) { + target = center_index - 1; + start_animation(); + } + } else { + target = fmax(0, center_index - 2); + } +} + +void show_next_slide(void) +{ + if (step <= 0) { + if (center_index < number_of_slides - 1) { + target = center_index + 1; + start_animation(); + } + } else { + target = fmin(center_index + 2, number_of_slides - 1); + } +} + + + +/** + Return true if the rect has size 0 +*/ +bool is_empty_rect(struct rect *r) +{ + return ((r->left == 0) && (r->right == 0) && (r->top == 0) + && (r->bottom == 0)); +} + + +/** + Render the slides. Updates only the offscreen buffer. +*/ +void render(void) +{ + rb->lcd_set_background(LCD_RGBPACK(0,0,0)); + rb->lcd_clear_display(); // TODO: Optimizes this by e.g. invalidating rects + + int nleft = LEFT_SLIDES_COUNT; + int nright = RIGHT_SLIDES_COUNT; + + struct rect r; + render_slide(¢er_slide, &r, 256, -1, -1); +#ifdef DEBUG_DRAW + rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top); +#endif + int c1 = r.left; + int c2 = r.right; + int index; + if (step == 0) { + // no animation, boring plain rendering + for (index = 0; index < nleft - 1; index++) { + int alpha = (index < nleft - 2) ? 256 : 128; + render_slide(&left_slides[index], &r, alpha, 0, c1 - 1); + if (!is_empty_rect(&r)) { +#ifdef DEBUG_DRAW + rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top); +#endif + c1 = r.left; + } + } + for (index = 0; index < nright - 1; index++) { + int alpha = (index < nright - 2) ? 256 : 128; + render_slide(&right_slides[index], &r, alpha, c2 + 1, + BUFFER_WIDTH); + if (!is_empty_rect(&r)) { +#ifdef DEBUG_DRAW + rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top); +#endif + c2 = r.right; + } + } + } else { + // the first and last slide must fade in/fade out + for (index = 0; index < nleft; index++) { + int alpha = 256; + if (index == nleft - 1) + alpha = (step > 0) ? 0 : 128 - fade / 2; + if (index == nleft - 2) + alpha = (step > 0) ? 128 - fade / 2 : 256 - fade / 2; + if (index == nleft - 3) + alpha = (step > 0) ? 256 - fade / 2 : 256; + render_slide(&left_slides[index], &r, alpha, 0, c1 - 1); + + if (!is_empty_rect(&r)) { +#ifdef DEBUG_DRAW + rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top); +#endif + c1 = r.left; + } + } + for (index = 0; index < nright; index++) { + int alpha = (index < nright - 2) ? 256 : 128; + if (index == nright - 1) + alpha = (step > 0) ? fade / 2 : 0; + if (index == nright - 2) + alpha = (step > 0) ? 128 + fade / 2 : fade / 2; + if (index == nright - 3) + alpha = (step > 0) ? 256 : 128 + fade / 2; + render_slide(&right_slides[index], &r, alpha, c2 + 1, + BUFFER_WIDTH); + if (!is_empty_rect(&r)) { +#ifdef DEBUG_DRAW + rb->lcd_drawrect(r.left, r.top, r.right - r.left, r.bottom - r.top); +#endif + c2 = r.right; + } + } + } +} + + +/** + Updates the animation effect. Call this periodically from a timer. +*/ +void update_animation(void) +{ + if (!animation_is_active) + return; + if (step == 0) + return; + + int speed = 16384; + int i; + + // deaccelerate when approaching the target + if (true) { + const int max = 2 * 65536; + + int fi = slide_frame; + fi -= (target << 16); + if (fi < 0) + fi = -fi; + fi = fmin(fi, max); + + int ia = IANGLE_MAX * (fi - max / 2) / (max * 2); + speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE; + } + + slide_frame += speed * step; + + int index = slide_frame >> 16; + int pos = slide_frame & 0xffff; + int neg = 65536 - pos; + int tick = (step < 0) ? neg : pos; + PFreal ftick = (tick * PFREAL_ONE) >> 16; + + // the leftmost and rightmost slide must fade away + fade = pos / 256; + + if (step < 0) + index++; + if (center_index != index) { + center_index = index; + slide_frame = index << 16; + center_slide.slide_index = center_index; + for (i = 0; i < LEFT_SLIDES_COUNT; i++) + left_slides[i].slide_index = center_index - 1 - i; + for (i = 0; i < RIGHT_SLIDES_COUNT; i++) + right_slides[i].slide_index = center_index + 1 + i; + } + + center_slide.angle = (step * tick * itilt) >> 16; + center_slide.cx = -step * fmul(offsetX, ftick); + center_slide.cy = fmul(offsetY, ftick); + + if (center_index == target) { + reset_slides(); + animation_is_active = false; + step = 0; + fade = 256; + return; + } + + for (i = 0; i < LEFT_SLIDES_COUNT; i++) { + struct slide_data *si = &left_slides[i]; + si->angle = itilt; + si->cx = + -(offsetX + spacing * i * PFREAL_ONE + step * spacing * ftick); + si->cy = offsetY; + } + + for (i = 0; i < RIGHT_SLIDES_COUNT; i++) { + struct slide_data *si = &right_slides[i]; + si->angle = -itilt; + si->cx = + offsetX + spacing * i * PFREAL_ONE - step * spacing * ftick; + si->cy = offsetY; + } + + if (step > 0) { + PFreal ftick = (neg * PFREAL_ONE) >> 16; + right_slides[0].angle = -(neg * itilt) >> 16; + right_slides[0].cx = fmul(offsetX, ftick); + right_slides[0].cy = fmul(offsetY, ftick); + } else { + PFreal ftick = (pos * PFREAL_ONE) >> 16; + left_slides[0].angle = (pos * itilt) >> 16; + left_slides[0].cx = -fmul(offsetX, ftick); + left_slides[0].cy = fmul(offsetY, ftick); + } + + // must change direction ? + if (target < index) + if (step > 0) + step = -1; + if (target > index) + if (step < 0) + step = 1; +} + + +/** + Cleanup the plugin +*/ +void cleanup(void *parameter) +{ + (void) parameter; +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(rb); /* backlight control in lib/helper.c */ + + int i; + for (i = 0; i < SLIDE_CACHE_SIZE; i++) { + rb->bufclose(slide_cache_hid[i]); + } +} + + + +/** + Main function that also contain the main plasma + algorithm. + */ +int main(void) +{ + if (!create_pf_thread()) { + rb->splash(HZ, "Cannot create thread!"); + return PLUGIN_ERROR; + } + + int i; + + // initialize + for (i = 0; i < SLIDE_CACHE_SIZE; i++) { + slide_cache_hid[i] = -1; + slide_cache_touched[i] = 0; + } + slide_cache_in_use = 0; + buffer = rb->lcd_framebuffer; + animation_is_active = false; + zoom = 100; + center_index = 0; + slide_frame = 0; + step = 0; + target = 0; + fade = 256; + show_fps = true; + number_of_slides = 100; // this is the album count +/* + for (i = 0; i < SLIDE_CACHE_SIZE; i++) { + if (!read_bmp(i,i)) { + rb->splash(HZ, "error reading bmp"); + return PLUGIN_ERROR; + } + } + */ + read_bmp(-1,SLIDE_CACHE_SIZE); // empty slide hack... FIXME: make this prettier + + recalc_table(); + reset_slides(); + + char fpstxt[10]; + char albumtxt[30]; + int button; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + int frames = 0; + long last_update = *rb->current_tick; + long current_update; + long update_interval = 100; + int fps = 0; + int albumtxt_w, albumtxt_h; + while (true) { + current_update = *rb->current_tick; + frames++; + update_animation(); + render(); + + if (current_update - last_update > update_interval) { + fps = frames * HZ / (current_update - last_update); + last_update = current_update; + frames = 0; + } + + if (show_fps) { + rb->lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps); + rb->lcd_putsxy(0, 0, fpstxt); + } + + rb->snprintf(albumtxt, sizeof(albumtxt), "Index is %d", center_index); // replace with album title + rb->lcd_set_foreground(LCD_RGBPACK(255, 255, 255)); + rb->lcd_getstringsize(albumtxt, &albumtxt_w, &albumtxt_h); + rb->lcd_putsxy((LCD_WIDTH - albumtxt_w) /2, LCD_HEIGHT-albumtxt_h-10, albumtxt); + + + + rb->lcd_update(); + rb->yield(); + + button = rb->button_get(false); //always paint -> sucks our battery + //button = rb->button_get(!animation_is_active); // we need to wake up here if our thread yells + switch (button) { + case PICTUREFLOW_QUIT: + cleanup(NULL); + return PLUGIN_OK; + break; + + case PICTUREFLOW_NEXT_ALBUM: + case (PICTUREFLOW_NEXT_ALBUM | BUTTON_REPEAT): + show_next_slide(); + break; + + case PICTUREFLOW_PREV_ALBUM: + case (PICTUREFLOW_PREV_ALBUM | BUTTON_REPEAT): + show_previous_slide(); + break; + + default: + if (rb->default_event_handler_ex(button, cleanup, NULL) + == SYS_USB_CONNECTED) + return PLUGIN_USB_CONNECTED; + break; + } + //rb->sleep(HZ/100); + } +} + +/*************************** Plugin entry point ****************************/ + +enum plugin_status plugin_start(struct plugin_api *api, void *parameter) +{ + int ret; + + rb = api; // copy to global api pointer + (void) parameter; +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop(NULL); +#endif + /* Turn off backlight timeout */ + backlight_force_on(rb); /* backlight control in lib/helper.c */ + + ret = main(); + end_pf_thread(); + return ret; +} + +#endif /* #ifdef HAVE_LCD_BITMAP */