nuclear@2: /* nuclear@2: colcycle - color cycling image viewer nuclear@2: Copyright (C) 2016-2017 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: #include nuclear@2: #if defined(__WATCOMC__) || defined(_MSC_VER) || defined(WIN32) nuclear@2: #include nuclear@2: #else nuclear@2: #include nuclear@2: #endif nuclear@2: #include "image.h" nuclear@2: #include "imagelbm.h" nuclear@2: nuclear@2: #ifndef M_PI nuclear@2: #define M_PI 3.141593 nuclear@2: #endif nuclear@2: nuclear@2: static int flatten_crange_list(struct image *img); nuclear@2: nuclear@2: int colc_gen_test_image(struct image *img) nuclear@2: { nuclear@2: int i, j; nuclear@2: unsigned char *pptr; nuclear@2: nuclear@2: img->width = 640; nuclear@2: img->height = 480; nuclear@2: img->bpp = 8; nuclear@2: nuclear@2: if(!(img->range = malloc(sizeof *img->range))) { nuclear@2: return -1; nuclear@2: } nuclear@2: img->num_ranges = 1; nuclear@2: img->range[0].low = 0; nuclear@2: img->range[0].high = 255; nuclear@2: img->range[0].cmode = CYCLE_NORMAL; nuclear@2: img->range[0].rate = 5000; nuclear@2: nuclear@2: if(!(img->pixels = malloc(img->width * img->height))) { nuclear@2: free(img->range); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: for(i=0; i<256; i++) { nuclear@2: float theta = M_PI * 2.0 * (float)i / 256.0; nuclear@2: float r = cos(theta) * 0.5 + 0.5; nuclear@2: float g = sin(theta) * 0.5 + 0.5; nuclear@2: float b = -cos(theta) * 0.5 + 0.5; nuclear@2: img->palette[i].r = (int)(r * 255.0); nuclear@2: img->palette[i].g = (int)(g * 255.0); nuclear@2: img->palette[i].b = (int)(b * 255.0); nuclear@2: } nuclear@2: nuclear@2: pptr = img->pixels; nuclear@2: for(i=0; iheight; i++) { nuclear@2: int c = (i << 8) / img->height; nuclear@2: for(j=0; jwidth; j++) { nuclear@2: int chess = ((i >> 6) & 1) == ((j >> 6) & 1); nuclear@2: *pptr++ = (chess ? c : c + 128) & 0xff; nuclear@2: } nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: #define MAX_TOKEN_SIZE 256 nuclear@2: static int image_block(FILE *fp, struct image *img); nuclear@2: static int nextc = -1; nuclear@2: static char token[MAX_TOKEN_SIZE]; nuclear@2: nuclear@2: int colc_load_image(struct image *img, const char *fname) nuclear@2: { nuclear@2: FILE *fp; nuclear@2: int c; nuclear@2: nuclear@2: if(!(fp = fopen(fname, "rb"))) { nuclear@2: fprintf(stderr, "failed to open file: %s: %s\n", fname, strerror(errno)); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: if(file_is_lbm(fp)) { nuclear@2: if(load_image_lbm(img, fp) == -1) { nuclear@2: fclose(fp); nuclear@2: return -1; nuclear@2: } nuclear@2: fclose(fp); nuclear@2: flatten_crange_list(img); nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: /* find the start of the root block */ nuclear@2: while((c = fgetc(fp)) != -1 && c != '{'); nuclear@2: if(feof(fp)) { nuclear@2: fprintf(stderr, "invalid image format, no image block found: %s\n", fname); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: nextc = c; /* prime the pump with the extra char we read above */ nuclear@2: if(image_block(fp, img) == -1) { nuclear@2: fprintf(stderr, "failed to read image block from: %s\n", fname); nuclear@2: fclose(fp); nuclear@2: return -1; nuclear@2: } nuclear@2: fclose(fp); nuclear@2: nuclear@2: flatten_crange_list(img); nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: void colc_destroy_image(struct image *img) nuclear@2: { nuclear@2: if(img) { nuclear@2: free(img->pixels); nuclear@2: free(img->range); nuclear@2: memset(img, 0, sizeof *img); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: static int flatten_crange_list(struct image *img) nuclear@2: { nuclear@2: struct colrange *list = img->range; nuclear@2: struct colrange *rptr; nuclear@2: nuclear@2: if(img->num_ranges <= 0) { nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: if(!(img->range = malloc(img->num_ranges * sizeof *img->range))) { nuclear@2: perror("flatten_crange_list: failed to allocate range array\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: rptr = img->range; nuclear@2: while(list) { nuclear@2: struct colrange *rng = list; nuclear@2: list = list->next; nuclear@2: *rptr++ = *rng; nuclear@2: free(rng); nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: /* ---- parser ---- */ nuclear@2: nuclear@2: enum { nuclear@2: TOKEN_NUM, nuclear@2: TOKEN_NAME, nuclear@2: TOKEN_STR nuclear@2: }; nuclear@2: nuclear@2: static int next_char(FILE *fp) nuclear@2: { nuclear@2: while((nextc = fgetc(fp)) != -1 && isspace(nextc)); nuclear@2: return nextc; nuclear@2: } nuclear@2: nuclear@2: static int next_token(FILE *fp) nuclear@2: { nuclear@2: char *ptr; nuclear@2: nuclear@2: if(nextc == -1) { nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: switch(nextc) { nuclear@2: case '{': nuclear@2: case '}': nuclear@2: case ',': nuclear@2: case '[': nuclear@2: case ']': nuclear@2: case ':': nuclear@2: token[0] = nextc; nuclear@2: token[1] = 0; nuclear@2: nextc = next_char(fp); nuclear@2: return token[0]; nuclear@2: nuclear@2: case '\'': nuclear@2: ptr = token; nuclear@2: nextc = next_char(fp); nuclear@2: while(nextc != -1 && nextc != '\'') { nuclear@2: *ptr++ = nextc; nuclear@2: nextc = fgetc(fp); nuclear@2: } nuclear@2: nextc = next_char(fp); nuclear@2: return TOKEN_STR; nuclear@2: nuclear@2: default: nuclear@2: break; nuclear@2: } nuclear@2: nuclear@2: if(isalpha(nextc)) { nuclear@2: ptr = token; nuclear@2: while(nextc != -1 && isalpha(nextc)) { nuclear@2: *ptr++ = nextc; nuclear@2: nextc = next_char(fp); nuclear@2: } nuclear@2: *ptr = 0; nuclear@2: return TOKEN_NAME; nuclear@2: } nuclear@2: if(isdigit(nextc)) { nuclear@2: ptr = token; nuclear@2: while(nextc != -1 && isdigit(nextc)) { nuclear@2: *ptr++ = nextc; nuclear@2: nextc = next_char(fp); nuclear@2: } nuclear@2: *ptr = 0; nuclear@2: return TOKEN_NUM; nuclear@2: } nuclear@2: nuclear@2: token[0] = nextc; nuclear@2: token[1] = 0; nuclear@2: fprintf(stderr, "next_token: unexpected character: %c\n", nextc); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: static int expect(FILE *fp, int tok) nuclear@2: { nuclear@2: if(next_token(fp) != tok) { nuclear@2: return 0; nuclear@2: } nuclear@2: return 1; nuclear@2: } nuclear@2: nuclear@2: static const char *toktypestr(int tok) nuclear@2: { nuclear@2: static char buf[] = "' '"; nuclear@2: switch(tok) { nuclear@2: case TOKEN_NUM: nuclear@2: return "number"; nuclear@2: case TOKEN_NAME: nuclear@2: return "name"; nuclear@2: case TOKEN_STR: nuclear@2: return "string"; nuclear@2: default: nuclear@2: break; nuclear@2: } nuclear@2: buf[1] = tok; nuclear@2: return buf; nuclear@2: } nuclear@2: nuclear@2: #define EXPECT(fp, x) \ nuclear@2: do { \ nuclear@2: if(!expect(fp, x)) { \ nuclear@2: fprintf(stderr, "%s: expected: %s, found: %s\n", __func__, toktypestr(x), token); \ nuclear@2: return -1; \ nuclear@2: } \ nuclear@2: } while(0) nuclear@2: nuclear@2: static int palette(FILE *fp, struct image *img) nuclear@2: { nuclear@2: int tok, cidx = 0; nuclear@2: struct color col, *cptr; nuclear@2: nuclear@2: EXPECT(fp, '['); nuclear@2: nuclear@2: while((tok = next_token(fp)) == '[') { nuclear@2: cptr = cidx < 256 ? &img->palette[cidx] : &col; nuclear@2: ++cidx; nuclear@2: nuclear@2: EXPECT(fp, TOKEN_NUM); nuclear@2: cptr->r = atoi(token); nuclear@2: EXPECT(fp, ','); nuclear@2: EXPECT(fp, TOKEN_NUM); nuclear@2: cptr->g = atoi(token); nuclear@2: EXPECT(fp, ','); nuclear@2: EXPECT(fp, TOKEN_NUM); nuclear@2: cptr->b = atoi(token); nuclear@2: EXPECT(fp, ']'); nuclear@2: nuclear@2: if(nextc == ',') { nuclear@2: next_token(fp); /* skip comma */ nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: if(tok != ']') { nuclear@2: fprintf(stderr, "palette must be closed by a ']' token\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int crange(FILE *fp, struct colrange *rng) nuclear@2: { nuclear@2: int val; nuclear@2: char name[MAX_TOKEN_SIZE]; nuclear@2: nuclear@2: EXPECT(fp, '{'); nuclear@2: nuclear@2: while(nextc != -1 && nextc != '}') { nuclear@2: EXPECT(fp, TOKEN_NAME); nuclear@2: strcpy(name, token); nuclear@2: EXPECT(fp, ':'); nuclear@2: EXPECT(fp, TOKEN_NUM); nuclear@2: val = atoi(token); nuclear@2: nuclear@2: if(strcmp(name, "reverse") == 0) { nuclear@2: rng->cmode = val; nuclear@2: } else if(strcmp(name, "rate") == 0) { nuclear@2: rng->rate = val; nuclear@2: } else if(strcmp(name, "low") == 0) { nuclear@2: rng->low = val; nuclear@2: } else if(strcmp(name, "high") == 0) { nuclear@2: rng->high = val; nuclear@2: } else { nuclear@2: fprintf(stderr, "invalid attribute %s in cycles range\n", name); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: if(nextc == ',') { nuclear@2: next_token(fp); nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: EXPECT(fp, '}'); nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int cycles(FILE *fp, struct image *img) nuclear@2: { nuclear@2: struct colrange *list = 0, *rng; nuclear@2: nuclear@2: EXPECT(fp, '['); nuclear@2: nuclear@2: img->num_ranges = 0; nuclear@2: while(nextc == '{') { nuclear@2: if(!(rng = malloc(sizeof *rng))) { nuclear@2: perror("failed to allocate color range"); nuclear@2: goto err; nuclear@2: } nuclear@2: if(crange(fp, rng) == -1) { nuclear@2: free(rng); nuclear@2: goto err; nuclear@2: } nuclear@2: if(rng->low != rng->high && rng->rate > 0) { nuclear@2: rng->next = list; nuclear@2: list = rng; nuclear@2: ++img->num_ranges; nuclear@2: } else { nuclear@2: free(rng); nuclear@2: } nuclear@2: nuclear@2: if(nextc == ',') { nuclear@2: next_token(fp); /* eat the comma */ nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: img->range = list; nuclear@2: nuclear@2: if(!expect(fp, ']')) { nuclear@2: fprintf(stderr, "cycles: missing closing bracket\n"); nuclear@2: goto err; nuclear@2: } nuclear@2: return 0; nuclear@2: nuclear@2: err: nuclear@2: while(list) { nuclear@2: rng = list; nuclear@2: list = list->next; nuclear@2: free(rng); nuclear@2: } nuclear@2: img->num_ranges = 0; nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: static int pixels(FILE *fp, struct image *img) nuclear@2: { nuclear@2: int tok, num_pixels; nuclear@2: unsigned char *pptr; nuclear@2: nuclear@2: if(img->width <= 0 || img->height <= 0) { nuclear@2: fprintf(stderr, "pixel block found before defining the image dimensions!\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: num_pixels = img->width * img->height; nuclear@2: if(!(img->pixels = malloc(num_pixels))) { nuclear@2: perror("failed to allocate pixels"); nuclear@2: return -1; nuclear@2: } nuclear@2: pptr = img->pixels; nuclear@2: nuclear@2: EXPECT(fp, '['); nuclear@2: nuclear@2: while((tok = next_token(fp)) == TOKEN_NUM) { nuclear@2: if(pptr - img->pixels >= num_pixels) { nuclear@2: pptr = 0; nuclear@2: fprintf(stderr, "more pixel data provided than the specified image dimensions\n"); nuclear@2: } nuclear@2: if(!pptr) continue; nuclear@2: *pptr++ = atoi(token); nuclear@2: nuclear@2: if(nextc == ',') { nuclear@2: next_token(fp); /* eat comma */ nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: if(tok != ']') { nuclear@2: printf("pixels: missing closing bracket\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int attribute(FILE *fp, struct image *img) nuclear@2: { nuclear@2: char *attr_name; nuclear@2: nuclear@2: if(!expect(fp, TOKEN_NAME)) { nuclear@2: return -1; nuclear@2: } nuclear@2: attr_name = alloca(strlen(token) + 1); nuclear@2: strcpy(attr_name, token); nuclear@2: nuclear@2: if(!expect(fp, ':')) { nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: if(strcmp(attr_name, "filename") == 0) { nuclear@2: if(!expect(fp, TOKEN_STR)) { nuclear@2: fprintf(stderr, "attribute: filename should be a string\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: } else if(strcmp(attr_name, "width") == 0 || strcmp(attr_name, "height") == 0) { nuclear@2: int *dst = attr_name[0] == 'w' ? &img->width : &img->height; nuclear@2: if(!expect(fp, TOKEN_NUM)) { nuclear@2: fprintf(stderr, "attribute: %s should be a number\n", attr_name); nuclear@2: return -1; nuclear@2: } nuclear@2: *dst = atoi(token); nuclear@2: nuclear@2: } else if(strcmp(attr_name, "colors") == 0) { nuclear@2: if(palette(fp, img) == -1) { nuclear@2: fprintf(stderr, "attribute: colors should be a palette\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: } else if(strcmp(attr_name, "cycles") == 0) { nuclear@2: if(cycles(fp, img) == -1) { nuclear@2: fprintf(stderr, "attribute: cycles should be a list of palranges\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: } else if(strcmp(attr_name, "pixels") == 0) { nuclear@2: if(pixels(fp, img) == -1) { nuclear@2: fprintf(stderr, "attribute: pixels should be a list of indices\n"); nuclear@2: return -1; nuclear@2: } nuclear@2: nuclear@2: } else { nuclear@2: fprintf(stderr, "unknown attribute: %s\n", attr_name); nuclear@2: return -1; nuclear@2: } nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@2: static int image_block(FILE *fp, struct image *img) nuclear@2: { nuclear@2: EXPECT(fp, '{'); nuclear@2: nuclear@2: img->width = img->height = -1; nuclear@2: img->bpp = 8; nuclear@2: nuclear@2: while(attribute(fp, img) != -1) { nuclear@2: if(nextc == ',') { nuclear@2: next_token(fp); /* eat comma */ nuclear@2: } nuclear@2: } nuclear@2: return 0; nuclear@2: }