nuclear@0: /* nuclear@0: colcycle - color cycling image viewer nuclear@0: Copyright (C) 2016-2017 John Tsiombikas nuclear@0: nuclear@0: This program is free software: you can redistribute it and/or modify nuclear@0: it under the terms of the GNU General Public License as published by nuclear@0: the Free Software Foundation, either version 3 of the License, or nuclear@0: (at your option) any later version. nuclear@0: nuclear@0: This program is distributed in the hope that it will be useful, nuclear@0: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@0: GNU General Public License for more details. nuclear@0: nuclear@0: You should have received a copy of the GNU General Public License nuclear@0: along with this program. If not, see . nuclear@0: */ nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #if defined(__WATCOMC__) || defined(_MSC_VER) || defined(WIN32) nuclear@0: #include nuclear@0: #else nuclear@0: #include nuclear@0: #endif nuclear@0: #include "image.h" nuclear@0: #include "imagelbm.h" nuclear@0: nuclear@0: #ifndef M_PI nuclear@0: #define M_PI 3.141593 nuclear@0: #endif nuclear@0: nuclear@0: static int flatten_crange_list(struct image *img); nuclear@0: nuclear@0: int gen_test_image(struct image *img) nuclear@0: { nuclear@0: int i, j; nuclear@0: unsigned char *pptr; nuclear@0: nuclear@0: img->width = 640; nuclear@0: img->height = 480; nuclear@0: img->bpp = 8; nuclear@0: nuclear@0: if(!(img->range = malloc(sizeof *img->range))) { nuclear@0: return -1; nuclear@0: } nuclear@0: img->num_ranges = 1; nuclear@0: img->range[0].low = 0; nuclear@0: img->range[0].high = 255; nuclear@0: img->range[0].cmode = CYCLE_NORMAL; nuclear@0: img->range[0].rate = 5000; nuclear@0: nuclear@0: if(!(img->pixels = malloc(img->width * img->height))) { nuclear@0: free(img->range); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: for(i=0; i<256; i++) { nuclear@0: float theta = M_PI * 2.0 * (float)i / 256.0; nuclear@0: float r = cos(theta) * 0.5 + 0.5; nuclear@0: float g = sin(theta) * 0.5 + 0.5; nuclear@0: float b = -cos(theta) * 0.5 + 0.5; nuclear@0: img->palette[i].r = (int)(r * 255.0); nuclear@0: img->palette[i].g = (int)(g * 255.0); nuclear@0: img->palette[i].b = (int)(b * 255.0); nuclear@0: } nuclear@0: nuclear@0: pptr = img->pixels; nuclear@0: for(i=0; iheight; i++) { nuclear@0: int c = (i << 8) / img->height; nuclear@0: for(j=0; jwidth; j++) { nuclear@0: int chess = ((i >> 6) & 1) == ((j >> 6) & 1); nuclear@0: *pptr++ = (chess ? c : c + 128) & 0xff; nuclear@0: } nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: #define MAX_TOKEN_SIZE 256 nuclear@0: static int image_block(FILE *fp, struct image *img); nuclear@0: static int nextc = -1; nuclear@0: static char token[MAX_TOKEN_SIZE]; nuclear@0: nuclear@0: int load_image(struct image *img, const char *fname) nuclear@0: { nuclear@0: FILE *fp; nuclear@0: int c; nuclear@0: nuclear@0: if(!(fp = fopen(fname, "rb"))) { nuclear@0: fprintf(stderr, "failed to open file: %s: %s\n", fname, strerror(errno)); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: if(file_is_lbm(fp)) { nuclear@0: if(load_image_lbm(img, fp) == -1) { nuclear@0: fclose(fp); nuclear@0: return -1; nuclear@0: } nuclear@0: fclose(fp); nuclear@0: flatten_crange_list(img); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: /* find the start of the root block */ nuclear@0: while((c = fgetc(fp)) != -1 && c != '{'); nuclear@0: if(feof(fp)) { nuclear@0: fprintf(stderr, "invalid image format, no image block found: %s\n", fname); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: nextc = c; /* prime the pump with the extra char we read above */ nuclear@0: if(image_block(fp, img) == -1) { nuclear@0: fprintf(stderr, "failed to read image block from: %s\n", fname); nuclear@0: fclose(fp); nuclear@0: return -1; nuclear@0: } nuclear@0: fclose(fp); nuclear@0: nuclear@0: flatten_crange_list(img); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: void destroy_image(struct image *img) nuclear@0: { nuclear@0: if(img) { nuclear@0: free(img->pixels); nuclear@0: free(img->range); nuclear@0: memset(img, 0, sizeof *img); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static int flatten_crange_list(struct image *img) nuclear@0: { nuclear@0: struct colrange *list = img->range; nuclear@0: struct colrange *rptr; nuclear@0: nuclear@0: if(img->num_ranges <= 0) { nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: if(!(img->range = malloc(img->num_ranges * sizeof *img->range))) { nuclear@0: perror("flatten_crange_list: failed to allocate range array\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: rptr = img->range; nuclear@0: while(list) { nuclear@0: struct colrange *rng = list; nuclear@0: list = list->next; nuclear@0: *rptr++ = *rng; nuclear@0: free(rng); nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: /* ---- parser ---- */ nuclear@0: nuclear@0: enum { nuclear@0: TOKEN_NUM, nuclear@0: TOKEN_NAME, nuclear@0: TOKEN_STR nuclear@0: }; nuclear@0: nuclear@0: static int next_char(FILE *fp) nuclear@0: { nuclear@0: while((nextc = fgetc(fp)) != -1 && isspace(nextc)); nuclear@0: return nextc; nuclear@0: } nuclear@0: nuclear@0: static int next_token(FILE *fp) nuclear@0: { nuclear@0: char *ptr; nuclear@0: nuclear@0: if(nextc == -1) { nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: switch(nextc) { nuclear@0: case '{': nuclear@0: case '}': nuclear@0: case ',': nuclear@0: case '[': nuclear@0: case ']': nuclear@0: case ':': nuclear@0: token[0] = nextc; nuclear@0: token[1] = 0; nuclear@0: nextc = next_char(fp); nuclear@0: return token[0]; nuclear@0: nuclear@0: case '\'': nuclear@0: ptr = token; nuclear@0: nextc = next_char(fp); nuclear@0: while(nextc != -1 && nextc != '\'') { nuclear@0: *ptr++ = nextc; nuclear@0: nextc = fgetc(fp); nuclear@0: } nuclear@0: nextc = next_char(fp); nuclear@0: return TOKEN_STR; nuclear@0: nuclear@0: default: nuclear@0: break; nuclear@0: } nuclear@0: nuclear@0: if(isalpha(nextc)) { nuclear@0: ptr = token; nuclear@0: while(nextc != -1 && isalpha(nextc)) { nuclear@0: *ptr++ = nextc; nuclear@0: nextc = next_char(fp); nuclear@0: } nuclear@0: *ptr = 0; nuclear@0: return TOKEN_NAME; nuclear@0: } nuclear@0: if(isdigit(nextc)) { nuclear@0: ptr = token; nuclear@0: while(nextc != -1 && isdigit(nextc)) { nuclear@0: *ptr++ = nextc; nuclear@0: nextc = next_char(fp); nuclear@0: } nuclear@0: *ptr = 0; nuclear@0: return TOKEN_NUM; nuclear@0: } nuclear@0: nuclear@0: token[0] = nextc; nuclear@0: token[1] = 0; nuclear@0: fprintf(stderr, "next_token: unexpected character: %c\n", nextc); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: static int expect(FILE *fp, int tok) nuclear@0: { nuclear@0: if(next_token(fp) != tok) { nuclear@0: return 0; nuclear@0: } nuclear@0: return 1; nuclear@0: } nuclear@0: nuclear@0: static const char *toktypestr(int tok) nuclear@0: { nuclear@0: static char buf[] = "' '"; nuclear@0: switch(tok) { nuclear@0: case TOKEN_NUM: nuclear@0: return "number"; nuclear@0: case TOKEN_NAME: nuclear@0: return "name"; nuclear@0: case TOKEN_STR: nuclear@0: return "string"; nuclear@0: default: nuclear@0: break; nuclear@0: } nuclear@0: buf[1] = tok; nuclear@0: return buf; nuclear@0: } nuclear@0: nuclear@0: #define EXPECT(fp, x) \ nuclear@0: do { \ nuclear@0: if(!expect(fp, x)) { \ nuclear@0: fprintf(stderr, "%s: expected: %s, found: %s\n", __FUNCTION__, toktypestr(x), token); \ nuclear@0: return -1; \ nuclear@0: } \ nuclear@0: } while(0) nuclear@0: nuclear@0: static int palette(FILE *fp, struct image *img) nuclear@0: { nuclear@0: int tok, cidx = 0; nuclear@0: struct color col, *cptr; nuclear@0: nuclear@0: EXPECT(fp, '['); nuclear@0: nuclear@0: while((tok = next_token(fp)) == '[') { nuclear@0: cptr = cidx < 256 ? &img->palette[cidx] : &col; nuclear@0: ++cidx; nuclear@0: nuclear@0: EXPECT(fp, TOKEN_NUM); nuclear@0: cptr->r = atoi(token); nuclear@0: EXPECT(fp, ','); nuclear@0: EXPECT(fp, TOKEN_NUM); nuclear@0: cptr->g = atoi(token); nuclear@0: EXPECT(fp, ','); nuclear@0: EXPECT(fp, TOKEN_NUM); nuclear@0: cptr->b = atoi(token); nuclear@0: EXPECT(fp, ']'); nuclear@0: nuclear@0: if(nextc == ',') { nuclear@0: next_token(fp); /* skip comma */ nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(tok != ']') { nuclear@0: fprintf(stderr, "palette must be closed by a ']' token\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: static int crange(FILE *fp, struct colrange *rng) nuclear@0: { nuclear@0: int val; nuclear@0: char name[MAX_TOKEN_SIZE]; nuclear@0: nuclear@0: EXPECT(fp, '{'); nuclear@0: nuclear@0: while(nextc != -1 && nextc != '}') { nuclear@0: EXPECT(fp, TOKEN_NAME); nuclear@0: strcpy(name, token); nuclear@0: EXPECT(fp, ':'); nuclear@0: EXPECT(fp, TOKEN_NUM); nuclear@0: val = atoi(token); nuclear@0: nuclear@0: if(strcmp(name, "reverse") == 0) { nuclear@0: rng->cmode = val; nuclear@0: } else if(strcmp(name, "rate") == 0) { nuclear@0: rng->rate = val; nuclear@0: } else if(strcmp(name, "low") == 0) { nuclear@0: rng->low = val; nuclear@0: } else if(strcmp(name, "high") == 0) { nuclear@0: rng->high = val; nuclear@0: } else { nuclear@0: fprintf(stderr, "invalid attribute %s in cycles range\n", name); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: if(nextc == ',') { nuclear@0: next_token(fp); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: EXPECT(fp, '}'); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: static int cycles(FILE *fp, struct image *img) nuclear@0: { nuclear@0: struct colrange *list = 0, *rng; nuclear@0: nuclear@0: EXPECT(fp, '['); nuclear@0: nuclear@0: img->num_ranges = 0; nuclear@0: while(nextc == '{') { nuclear@0: if(!(rng = malloc(sizeof *rng))) { nuclear@0: perror("failed to allocate color range"); nuclear@0: goto err; nuclear@0: } nuclear@0: if(crange(fp, rng) == -1) { nuclear@0: free(rng); nuclear@0: goto err; nuclear@0: } nuclear@0: if(rng->low != rng->high && rng->rate > 0) { nuclear@0: rng->next = list; nuclear@0: list = rng; nuclear@0: ++img->num_ranges; nuclear@0: } else { nuclear@0: free(rng); nuclear@0: } nuclear@0: nuclear@0: if(nextc == ',') { nuclear@0: next_token(fp); /* eat the comma */ nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: img->range = list; nuclear@0: nuclear@0: if(!expect(fp, ']')) { nuclear@0: fprintf(stderr, "cycles: missing closing bracket\n"); nuclear@0: goto err; nuclear@0: } nuclear@0: return 0; nuclear@0: nuclear@0: err: nuclear@0: while(list) { nuclear@0: rng = list; nuclear@0: list = list->next; nuclear@0: free(rng); nuclear@0: } nuclear@0: img->num_ranges = 0; nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: static int pixels(FILE *fp, struct image *img) nuclear@0: { nuclear@0: int tok, num_pixels; nuclear@0: unsigned char *pptr; nuclear@0: nuclear@0: if(img->width <= 0 || img->height <= 0) { nuclear@0: fprintf(stderr, "pixel block found before defining the image dimensions!\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: num_pixels = img->width * img->height; nuclear@0: if(!(img->pixels = malloc(num_pixels))) { nuclear@0: perror("failed to allocate pixels"); nuclear@0: return -1; nuclear@0: } nuclear@0: pptr = img->pixels; nuclear@0: nuclear@0: EXPECT(fp, '['); nuclear@0: nuclear@0: while((tok = next_token(fp)) == TOKEN_NUM) { nuclear@0: if(pptr - img->pixels >= num_pixels) { nuclear@0: pptr = 0; nuclear@0: fprintf(stderr, "more pixel data provided than the specified image dimensions\n"); nuclear@0: } nuclear@0: if(!pptr) continue; nuclear@0: *pptr++ = atoi(token); nuclear@0: nuclear@0: if(nextc == ',') { nuclear@0: next_token(fp); /* eat comma */ nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(tok != ']') { nuclear@0: printf("pixels: missing closing bracket\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: static int attribute(FILE *fp, struct image *img) nuclear@0: { nuclear@0: char *attr_name; nuclear@0: nuclear@0: if(!expect(fp, TOKEN_NAME)) { nuclear@0: return -1; nuclear@0: } nuclear@0: attr_name = alloca(strlen(token) + 1); nuclear@0: strcpy(attr_name, token); nuclear@0: nuclear@0: if(!expect(fp, ':')) { nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: if(strcmp(attr_name, "filename") == 0) { nuclear@0: if(!expect(fp, TOKEN_STR)) { nuclear@0: fprintf(stderr, "attribute: filename should be a string\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: } else if(strcmp(attr_name, "width") == 0 || strcmp(attr_name, "height") == 0) { nuclear@0: int *dst = attr_name[0] == 'w' ? &img->width : &img->height; nuclear@0: if(!expect(fp, TOKEN_NUM)) { nuclear@0: fprintf(stderr, "attribute: %s should be a number\n", attr_name); nuclear@0: return -1; nuclear@0: } nuclear@0: *dst = atoi(token); nuclear@0: nuclear@0: } else if(strcmp(attr_name, "colors") == 0) { nuclear@0: if(palette(fp, img) == -1) { nuclear@0: fprintf(stderr, "attribute: colors should be a palette\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: } else if(strcmp(attr_name, "cycles") == 0) { nuclear@0: if(cycles(fp, img) == -1) { nuclear@0: fprintf(stderr, "attribute: cycles should be a list of palranges\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: } else if(strcmp(attr_name, "pixels") == 0) { nuclear@0: if(pixels(fp, img) == -1) { nuclear@0: fprintf(stderr, "attribute: pixels should be a list of indices\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: } else { nuclear@0: fprintf(stderr, "unknown attribute: %s\n", attr_name); nuclear@0: return -1; nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: static int image_block(FILE *fp, struct image *img) nuclear@0: { nuclear@0: EXPECT(fp, '{'); nuclear@0: nuclear@0: img->width = img->height = -1; nuclear@0: img->bpp = 8; nuclear@0: nuclear@0: while(attribute(fp, img) != -1) { nuclear@0: if(nextc == ',') { nuclear@0: next_token(fp); /* eat comma */ nuclear@0: } nuclear@0: } nuclear@0: return 0; nuclear@0: }