nuclear@2: /* nuclear@2: colcycle - color cycling image viewer nuclear@2: Copyright (C) 2016 John Tsiombikas nuclear@2: nuclear@2: This program is free software: you can redistribute it and/or modify nuclear@2: it under the terms of the GNU General Public License as published by nuclear@2: the Free Software Foundation, either version 3 of the License, or nuclear@2: (at your option) any later version. nuclear@2: nuclear@2: This program is distributed in the hope that it will be useful, nuclear@2: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@2: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@2: GNU General Public License for more details. nuclear@2: nuclear@2: You should have received a copy of the GNU General Public License nuclear@2: along with this program. If not, see . nuclear@2: */ nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #ifdef __WATCOMC__ nuclear@2: #include "inttypes.h" nuclear@2: #include nuclear@2: #else nuclear@2: #include nuclear@2: #include nuclear@2: #endif nuclear@2: #include nuclear@2: #include "app.h" nuclear@2: #include "image.h" nuclear@2: nuclear@2: /* define this if the assembly 32->64bit multiplication in cycle_offset doesn't nuclear@2: * work for you for some reason, and you wish to compile the floating point nuclear@2: * version of the time calculation. nuclear@2: */ nuclear@2: #undef NO_ASM nuclear@2: nuclear@2: #if defined(NO_ASM) || (!defined(__i386__) && !defined(__x86_64__) && !defined(__X86__)) nuclear@2: #define USE_FLOAT nuclear@2: #endif nuclear@2: nuclear@2: nuclear@2: #ifndef M_PI nuclear@2: #define M_PI 3.141593 nuclear@2: #endif nuclear@2: nuclear@2: struct ss_node { nuclear@2: char *path; nuclear@2: struct image *img; nuclear@2: struct ss_node *next; nuclear@2: }; nuclear@2: nuclear@2: static void set_image_palette(struct image *img); nuclear@2: static void show_image(struct image *img, long time_msec); nuclear@2: static int load_slideshow(const char *path); nuclear@2: static int load_slide(void); nuclear@2: nuclear@2: int fbwidth, fbheight; nuclear@2: unsigned char *fbpixels; nuclear@2: nuclear@2: static struct image *img; nuclear@2: static int blend = 1; nuclear@2: static int fade_dir; nuclear@2: static unsigned long fade_start, fade_dur = 600; nuclear@2: static int change_pending; nuclear@2: static unsigned long showing_since, show_time = 15000; nuclear@2: nuclear@2: static struct ss_node *sslist; nuclear@2: nuclear@2: extern long upd_interval; nuclear@2: nuclear@2: int colc_init(int argc, char **argv) nuclear@2: { nuclear@2: if(argv[1]) { nuclear@2: struct stat st; nuclear@2: nuclear@2: if(stat(argv[1], &st) == -1) { nuclear@2: fprintf(stderr, "failed to stat path: %s\n", argv[1]); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: if(S_ISDIR(st.st_mode)) { nuclear@2: if(load_slideshow(argv[1]) == -1) { nuclear@2: fprintf(stderr, "failed to load slideshow in dir: %s\n", argv[1]); nuclear@2: return -1; nuclear@2: } nuclear@2: if(load_slide() == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: } else { nuclear@2: if(!(img = malloc(sizeof *img))) { nuclear@2: perror("failed to allocate image structure"); nuclear@2: return -1; nuclear@2: } nuclear@2: if(colc_load_image(img, argv[1]) == -1) { nuclear@2: fprintf(stderr, "failed to load image: %s\n", argv[1]); nuclear@2: return -1; nuclear@2: } nuclear@2: } nuclear@2: } else { nuclear@2: if(!(img = malloc(sizeof *img))) { nuclear@2: perror("failed to allocate image structure"); nuclear@2: return -1; nuclear@2: } nuclear@2: if(colc_gen_test_image(img) == -1) { nuclear@2: fprintf(stderr, "failed to generate test image\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: set_image_palette(img); nuclear@2: show_image(img, 0); nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: void colc_cleanup(void) nuclear@2: { nuclear@2: if(sslist) { nuclear@2: struct ss_node *start = sslist; nuclear@2: sslist = sslist->next; nuclear@2: start->next = 0; /* break the circle */ nuclear@2: nuclear@2: while(sslist) { nuclear@2: struct ss_node *node = sslist; nuclear@2: sslist = sslist->next; nuclear@2: colc_destroy_image(node->img); nuclear@2: free(node->path); nuclear@2: free(node); nuclear@2: } nuclear@2: nuclear@2: } else { nuclear@2: colc_destroy_image(img); nuclear@2: free(img); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: /* tx is fixed point with 10 bits decimal */ nuclear@2: static void palfade(int dir, long tx) nuclear@2: { nuclear@2: int i; nuclear@2: nuclear@2: if(!img) return; nuclear@2: nuclear@2: if(dir == -1) { nuclear@2: tx = 1024 - tx; nuclear@2: } nuclear@2: nuclear@2: for(i=0; i<256; i++) { nuclear@2: int r = (img->palette[i].r * tx) >> 10; nuclear@2: int g = (img->palette[i].g * tx) >> 10; nuclear@2: int b = (img->palette[i].b * tx) >> 10; nuclear@2: set_palette(i, r, g, b); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: nuclear@2: /* modes: nuclear@2: * 1. normal nuclear@2: * 2. reverse nuclear@2: * 3. ping-pong nuclear@2: * 4. sine -> [0, rsize/2] nuclear@2: * 5. sine -> [0, rsize] nuclear@2: * nuclear@2: * returns: offset in 24.8 fixed point nuclear@2: */ nuclear@2: #ifdef USE_FLOAT nuclear@2: static int32_t cycle_offset(enum cycle_mode mode, int32_t rate, int32_t rsize, int32_t msec) nuclear@2: { nuclear@2: float offs; nuclear@2: float tm = (rate / 280.0) * (float)msec / 1000.0; nuclear@2: nuclear@2: switch(mode) { nuclear@2: case CYCLE_PINGPONG: nuclear@2: offs = fmod(tm, (float)(rsize * 2)); nuclear@2: if(offs >= rsize) offs = (rsize * 2) - offs; nuclear@2: break; nuclear@2: nuclear@2: case CYCLE_SINE: nuclear@2: case CYCLE_SINE_HALF: nuclear@2: { nuclear@2: float x = fmod(tm, (float)rsize); nuclear@2: offs = sin((x * M_PI * 2.0) / (float)rsize) + 1.0; nuclear@2: offs *= rsize / (mode == CYCLE_SINE_HALF ? 4.0f : 2.0f); nuclear@2: } nuclear@2: break; nuclear@2: nuclear@2: default: /* normal or reverse */ nuclear@2: offs = tm; nuclear@2: } nuclear@2: return (int32_t)(offs * 256.0); nuclear@2: } nuclear@2: nuclear@2: #else /* fixed point variant for specific platforms (currently x86) */ nuclear@2: nuclear@2: #ifdef __GNUC__ nuclear@2: #define CALC_TIME(res, anim_rate, time_msec) \ nuclear@2: asm volatile ( \ nuclear@2: "\n\tmull %1" /* edx:eax <- eax(rate << 8) * msec */ \ nuclear@2: "\n\tmovl $280000, %%ebx" \ nuclear@2: "\n\tdivl %%ebx" /* eax <- edx:eax / ebx */ \ nuclear@2: : "=a" (res) \ nuclear@2: : "g" ((uint32_t)(time_msec)), "a" ((anim_rate) << 8) \ nuclear@2: : "ebx", "edx" ) nuclear@2: #endif /* __GNUC__ */ nuclear@2: nuclear@2: #ifdef __WATCOMC__ nuclear@2: #define CALC_TIME(res, anim_rate, time_msec) \ nuclear@2: (res) = calc_time_offset(anim_rate, time_msec) nuclear@2: nuclear@2: int32_t calc_time_offset(int32_t anim_rate, int32_t time_msec); nuclear@2: #pragma aux calc_time_offset = \ nuclear@2: "shl eax, 8" /* eax(rate) <<= 8 convert to 24.8 fixed point */ \ nuclear@2: "mul ebx" /* edx:eax <- eax(rate<<8) * ebx(msec) */ \ nuclear@2: "mov ebx, 280000" \ nuclear@2: "div ebx" /* eax <- edx:eax / ebx */ \ nuclear@2: modify [eax ebx ecx] \ nuclear@2: value [eax] \ nuclear@2: parm [eax] [ebx]; nuclear@2: #endif /* __WATCOMC__ */ nuclear@2: nuclear@2: static int32_t cycle_offset(enum cycle_mode mode, int32_t rate, int32_t rsize, int32_t msec) nuclear@2: { nuclear@2: int32_t offs, tm; nuclear@2: nuclear@2: CALC_TIME(tm, rate, msec); nuclear@2: nuclear@2: switch(mode) { nuclear@2: case CYCLE_PINGPONG: nuclear@2: rsize <<= 8; /* rsize -> 24.8 fixed point */ nuclear@2: offs = tm % (rsize * 2); nuclear@2: if(offs > rsize) offs = (rsize * 2) - offs; nuclear@2: rsize >>= 8; /* back to 32.0 */ nuclear@2: break; nuclear@2: nuclear@2: case CYCLE_SINE: nuclear@2: case CYCLE_SINE_HALF: nuclear@2: { nuclear@2: float t = (float)tm / 256.0; /* convert fixed24.8 -> float */ nuclear@2: float x = fmod(t, (float)(rsize * 2)); nuclear@2: float foffs = sin((x * M_PI * 2.0) / (float)rsize) + 1.0; nuclear@2: if(mode == CYCLE_SINE_HALF) { nuclear@2: foffs *= rsize / 4.0; nuclear@2: } else { nuclear@2: foffs *= rsize / 2.0; nuclear@2: } nuclear@2: offs = (int32_t)(foffs * 256.0); /* convert float -> fixed24.8 */ nuclear@2: } nuclear@2: break; nuclear@2: nuclear@2: default: /* normal or reverse */ nuclear@2: offs = tm; nuclear@2: } nuclear@2: nuclear@2: return offs; nuclear@2: } nuclear@2: #endif /* USE_FLOAT */ nuclear@2: nuclear@2: #define LERP_FIXED_T(a, b, xt) ((((a) << 8) + ((b) - (a)) * (xt)) >> 8) nuclear@2: nuclear@2: void colc_draw(long time_msec) nuclear@2: { nuclear@2: int i, j; nuclear@2: nuclear@2: if(!img) return; nuclear@2: nuclear@2: if(sslist) { nuclear@2: /* if there is a slideshow list of images, handle switching with fades between them */ nuclear@2: if(!fade_dir && (change_pending || time_msec - showing_since > show_time)) { nuclear@2: fade_dir = -1; nuclear@2: fade_start = time_msec; nuclear@2: change_pending = 0; nuclear@2: } nuclear@2: nuclear@2: if(fade_dir) { nuclear@2: unsigned long dt = time_msec - fade_start; nuclear@2: nuclear@2: if(dt >= fade_dur) { nuclear@2: if(fade_dir == -1) { nuclear@2: sslist = sslist->next; nuclear@2: if(load_slide() == -1) { nuclear@2: return; nuclear@2: } nuclear@2: show_image(img, time_msec); nuclear@2: fade_dir = 1; nuclear@2: time_msec = fade_start = time_msec; nuclear@2: dt = 0; nuclear@2: } else { nuclear@2: set_image_palette(img); nuclear@2: fade_dir = 0; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: if(fade_dir) { nuclear@2: long tx = ((long)dt << 10) / fade_dur; nuclear@2: palfade(fade_dir, tx); nuclear@2: } nuclear@2: showing_since = time_msec; nuclear@2: return; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: /* for each cycling range in the image ... */ nuclear@2: for(i=0; inum_ranges; i++) { nuclear@2: int32_t offs, rsize, ioffs; nuclear@2: int rev; nuclear@2: nuclear@2: if(!img->range[i].rate) continue; nuclear@2: rsize = img->range[i].high - img->range[i].low + 1; nuclear@2: nuclear@2: offs = cycle_offset(img->range[i].cmode, img->range[i].rate, rsize, time_msec); nuclear@2: nuclear@2: ioffs = (offs >> 8) % rsize; nuclear@2: nuclear@2: /* reverse when rev is 2 */ nuclear@2: rev = img->range[i].cmode == CYCLE_REVERSE ? 1 : 0; nuclear@2: nuclear@2: for(j=0; jrange[i].low; nuclear@2: nuclear@2: if(rev) { nuclear@2: to = (j + ioffs) % rsize; nuclear@2: next = (to + 1) % rsize; nuclear@2: } else { nuclear@2: if((to = (j - ioffs) % rsize) < 0) { nuclear@2: to += rsize; nuclear@2: } nuclear@2: if((next = to - 1) < 0) { nuclear@2: next += rsize; nuclear@2: } nuclear@2: } nuclear@2: to += img->range[i].low; nuclear@2: nuclear@2: if(blend) { nuclear@2: int r, g, b; nuclear@2: int32_t frac_offs = offs & 0xff; nuclear@2: nuclear@2: next += img->range[i].low; nuclear@2: nuclear@2: r = LERP_FIXED_T(img->palette[to].r, img->palette[next].r, frac_offs); nuclear@2: g = LERP_FIXED_T(img->palette[to].g, img->palette[next].g, frac_offs); nuclear@2: b = LERP_FIXED_T(img->palette[to].b, img->palette[next].b, frac_offs); nuclear@2: nuclear@2: set_palette(pidx, r, g, b); nuclear@2: } else { nuclear@2: set_palette(pidx, img->palette[to].r, img->palette[to].g, img->palette[to].b); nuclear@2: } nuclear@2: } nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: nuclear@2: static void set_image_palette(struct image *img) nuclear@2: { nuclear@2: int i; nuclear@2: for(i=0; i<256; i++) { nuclear@2: set_palette(i, img->palette[i].r, img->palette[i].g, img->palette[i].b); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: static void show_image(struct image *img, long time_msec) nuclear@2: { nuclear@2: int i, j; nuclear@2: unsigned char *dptr; nuclear@2: unsigned int max_rate = 0; nuclear@2: nuclear@2: resize(img->width, img->height); nuclear@2: nuclear@2: dptr = fbpixels; nuclear@2: for(i=0; iheight && j < img->width) { nuclear@2: c = img->pixels[i * img->width + j]; nuclear@2: } nuclear@2: *dptr++ = c; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: showing_since = time_msec; nuclear@2: nuclear@2: for(i=0; inum_ranges; i++) { nuclear@2: if(img->range[i].rate > max_rate) { nuclear@2: max_rate = img->range[i].rate; nuclear@2: } nuclear@2: } nuclear@2: upd_interval = max_rate * 10; nuclear@2: } nuclear@2: nuclear@2: static int load_slideshow(const char *path) nuclear@2: { nuclear@2: DIR *dir; nuclear@2: struct dirent *dent; nuclear@2: struct ss_node *head = 0, *tail = 0, *node; nuclear@2: nuclear@2: if(!(dir = opendir(path))) { nuclear@2: fprintf(stderr, "failed to open directory: %s: %s\n", path, strerror(errno)); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: while((dent = readdir(dir))) { nuclear@2: int sz; nuclear@2: nuclear@2: if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) { nuclear@2: continue; nuclear@2: } nuclear@2: sz = strlen(path) + strlen(dent->d_name) + 1; /* +1 for a slash */ nuclear@2: nuclear@2: if(!(node = malloc(sizeof *node))) { nuclear@2: perror("failed to allocate slideshow node"); nuclear@2: goto err; nuclear@2: } nuclear@2: if(!(node->path = malloc(sz + 1))) { nuclear@2: perror("failed to allocate image path"); nuclear@2: free(node); nuclear@2: goto err; nuclear@2: } nuclear@2: sprintf(node->path, "%s/%s", path, dent->d_name); nuclear@2: node->img = 0; nuclear@2: node->next = 0; nuclear@2: nuclear@2: if(head) { nuclear@2: tail->next = node; nuclear@2: tail = node; nuclear@2: } else { nuclear@2: head = tail = node; nuclear@2: } nuclear@2: } nuclear@2: closedir(dir); nuclear@2: nuclear@2: sslist = head; nuclear@2: tail->next = head; /* make circular */ nuclear@2: return 0; nuclear@2: nuclear@2: err: nuclear@2: closedir(dir); nuclear@2: while(head) { nuclear@2: node = head; nuclear@2: head = head->next; nuclear@2: free(node->path); nuclear@2: free(node); nuclear@2: } nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: static int load_slide(void) nuclear@2: { nuclear@2: struct ss_node *start = sslist; nuclear@2: nuclear@2: img = 0; nuclear@2: do { nuclear@2: if(sslist->path) { nuclear@2: if(!sslist->img) { nuclear@2: if(!(sslist->img = malloc(sizeof *sslist->img))) { nuclear@2: perror("failed to allocate image structure"); nuclear@2: return -1; nuclear@2: } nuclear@2: if(colc_load_image(sslist->img, sslist->path) == -1) { nuclear@2: fprintf(stderr, "failed to load image: %s\n", sslist->path); nuclear@2: free(sslist->path); nuclear@2: sslist->path = 0; nuclear@2: free(sslist->img); nuclear@2: sslist->img = 0; nuclear@2: } nuclear@2: } nuclear@2: img = sslist->img; nuclear@2: } nuclear@2: nuclear@2: sslist = sslist->next; nuclear@2: } while(!img && sslist != start); nuclear@2: nuclear@2: return img ? 0 : -1; nuclear@2: }