Code:isochromic.c
(Redirected from Image generator)
// Elie's isochromic image generator v0.6
// Copyright 2022, Elie Goldman Smith
// License: GNU GPL V3
// To compile this code:
// gcc isochromic.c -lm -lfftw3 -lreadline -lcurses -O3 -o isochromic
// # takes awhile to compile, because of optimization and stb_image
#include <ctype.h>
#include <fftw3.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#ifdef _MSC_VER
#define STBI_MSC_SECURE_CRT
#endif
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
// all raster data is stored internally with type Pixel:
#ifdef LESS_PRECISION
typedef float Pixel;
#define IS_NAN isnanf
#define fftwx_plan fftwf_plan
#define fftwx_plan_r2r_2d fftwf_plan_r2r_2d
#define fftwx_execute fftwf_execute
#define fftwx_destroy_plan fftwf_destroy_plan
#else
typedef double Pixel;
#define IS_NAN isnan
#define fftwx_plan fftw_plan
#define fftwx_plan_r2r_2d fftw_plan_r2r_2d
#define fftwx_execute fftw_execute
#define fftwx_destroy_plan fftw_destroy_plan
#endif
#define MAXVARS 8192
int nVars=0;
typedef struct {
char *name;
int width, height;
Pixel *data;
} Var;
Var vars[MAXVARS] = {0};
#define RESULT_OK 0 //
#define RESULT_ERROR 1 // return values used in a few functions
#define RESULT_EXIT -1 //
void help() {
puts("Elie's isochromic image generator v0.5");
puts("Instructions are not fully written yet");
puts(" ");
puts("Commands - quick reference:");
puts(" foo << input.png # Load data from file (can create new variable 'foo')");
puts(" bar := foo # Copy data (can create new variable 'bar')");
puts(" bar += foo # Addition");
puts(" bar -= foo # Subtraction");
puts(" bar *= foo # Multiplication");
puts(" bar /= foo # Division");
puts(" bar ^= foo # Exponentiation");
puts(" bar <= foo # Max function");
puts(" bar >= foo # Min function");
puts(" bar _= foo # Fill in any blanks");
puts(" bar [] foo # Array lookup");
puts(" bar :: foo # Resize image (or create new blank variable 'bar')");
puts(" bar >> output.png # Save data to file");
puts(" -- ");
puts(" vars # List all variables");
puts(" help # Show help info");
puts(" done # Quit the program");
puts(" -- # Special functions:");
puts(" foo @@ spread # Smooth out the overflows");
puts(" foo @@ stats # Quick statistical info");
puts(" foo @@ clear # Delete 'foo', free up memory");
}
Var *find_var(char *name) {
for (int i=0; i<nVars; i++) if (!strcmp(name, vars[i].name)) return &vars[i];
return NULL;
}
Var *create_var(char *name, int width, int height) {
int nBytes = width * height * sizeof(Pixel);
Var *v = NULL;
for (int i=0; i<nVars; i++) if (!strcmp(name, vars[i].name)) {
v = &vars[i]; // variable exists - will be overwritten
free(v->data); // free existing memory
break;
}
if (!v) {
if (nVars < MAXVARS) {
v = &vars[nVars++]; // create new variable
v->name = strdup(name);
}
else printf("Too many variables - can't create another.\n");
}
v->data = malloc(nBytes);
if (v->data) {
v->width = width;
v->height = height;
}
else {
printf("Not enough memory for variable's new data (need %d MB)\n", (nBytes+500000)/1000000);
v->width=0; v->height=0; v=NULL;
}
return v;
}
int needsSpread(const Var *v) {
int size = v->width * v->height;
for (int i=0; i<size; i++) if (v->data[i] < 0.0 || v->data[i] > 1.0) return 1;
return 0;
}
volatile int _spread_interrupted = 0;
void _spread_ctrl_c(int sig) {
signal(SIGINT, SIG_IGN);
printf("\n\nSpread interrupted by CTRL-C\n\n");
_spread_interrupted = 1;
}
int spread(Pixel *output, const Pixel *input, int width, int height)
{
_spread_interrupted = 0;
if (width<3 || height<3) {
printf("Can't generate isochromic image - dimensions are too small.\n");
return RESULT_ERROR;
}
int size = width*height;
for (int i=0; i<size; i++) if (IS_NAN(input[i])) {
printf("Can't generate isochromic image - data contains blanks (NaNs)\n");
printf("Fill them in using the _= operator.\n");
return RESULT_ERROR;
}
Pixel sum=0; for (int i=0; i<size; i++) sum += input[i];
if (sum < 0.0 || sum > size) {
printf("Can't generate isochromic image - pixels average to %lf.\n", (double)sum/size);
return RESULT_ERROR;
}
Pixel *over = malloc(size*sizeof(Pixel)); // buffer for the overflows
Pixel *temp = malloc(size*sizeof(Pixel)); // temporary buffer used for box blur
if (!over || !temp) {
printf("Not enough memory\n");
free(over); free(temp);
return RESULT_ERROR;
}
signal(SIGINT, _spread_ctrl_c);
printf(" Spreading out the overflows - this may take awhile"); fflush(stdout);
if (output != input) memcpy(output, input, size*sizeof(Pixel));
printf("...\n");
int blurSize=1;
int passes=0;
while (!_spread_interrupted)
{
// transfer the overflows to over[]
sum=0;int count=0;
for (int i=0; i<size; i++) {
if (output[i] > 1.0) {
over[i] = output[i] - 1.0;
output[i] = 1.0;
sum += over[i]; if (over[i] > 0.01) count++;
}
else if (output[i] < 0.0) {
over[i] = output[i];
output[i] = 0.0;
sum -= over[i]; if (over[i] < -0.01) count++;
}
else over[i] = 0.0;
}
printf(" %d overflows, ", count);
if (count == 0) {
printf("done.\n");
break; // main exit point
}
printf("typically by %lf\n", (double)sum/count);
if (++passes > 128) {
printf("Too many passes. Not all the overflows are gone, but we have to move on.\n");
printf("Image won't be perfectly isochromic, but still pretty close.\n");
break;
}
// box-blur the overflows - horizontally (over -> temp)
printf(" Applying box blur radius %d", blurSize); fflush(stdout);
Pixel norm = 1.0 / (blurSize*2+1);
Pixel a=0; for (int i=0; i<=blurSize*2; i++) a += over[i];
Pixel *sub = &over[0];
Pixel *add = &over[blurSize*2+1];
Pixel *write=&temp[blurSize];
Pixel *end = &temp[size-blurSize];
for (int i=0; i<blurSize; i++) temp[i] = a*norm; // first pixels
while (1) {
*(write++) = a*norm; // main pixels (wrap rows)
if (write == end) break;
a += *(add++);
a -= *(sub++);
}
for (int i=size-blurSize; i<size; i++) temp[i] = a*norm; // last pixels
// box-blur the overflows - vertically (temp -> over)
printf("...\n");
for (int i=0; i<width && !_spread_interrupted; i++) { // for each column...
a=0; for (int j=0; j<=blurSize*2; j++) a += temp[i+j*width];
sub = &temp[i];
add = &temp[i + (blurSize*2+1)*width];
write=&over[i + blurSize*width];
end = &over[i + size - blurSize*width];
for (int j=0; j<blurSize; j++) over[i+j*width] = a*norm; // first pixels of column
while (1) {
*write = a*norm; write += width; // main pixels of column
if (write >= end) break;
a += *add; add += width;
a -= *sub; sub += width;
}
for (int j=blurSize; j>0; j--) over[size+i-j*width] = a*norm; // last pixels of column
}
// add back the blurred overflows
if (_spread_interrupted)for (int i=0; i<size; i++) output[i] += temp[i]; // rare case
else for (int i=0; i<size; i++) output[i] += over[i]; // main case
// blur will be bigger next iteration
blurSize = blurSize*1.4+1;
if (blurSize > width/3) blurSize = width/3;
if (blurSize > height/3) blurSize = height/3;
if (blurSize < 1) blurSize = 1;
}
free(temp);
free(over);
signal(SIGINT, SIG_DFL);
if (_spread_interrupted) return RESULT_ERROR;
return RESULT_OK;
}
int resize(Pixel **data, int oldWidth, int oldHeight, int newWidth, int newHeight, int preserveSum) // the 'data' param is a pointer to a pointer, because it may be modified to point to another chunk of memory with the new size
{
if (oldWidth==newWidth && oldHeight==newHeight) return RESULT_OK;
int oldSize = oldWidth * oldHeight;
int newSize = newWidth * newHeight;
if (oldSize==0 || newSize==0) return RESULT_ERROR;
// memory management
Pixel *freq = malloc(sizeof(Pixel) * (oldSize > newSize ? oldSize : newSize));
if ( !freq ) return RESULT_ERROR;
if (newSize > oldSize) {
Pixel *nb = realloc(*data, newSize*sizeof(Pixel));
if (!nb) { free(freq); return RESULT_ERROR; }
*data = nb;
}
// get the frequency data from the image
fftwx_plan plan = fftwx_plan_r2r_2d(oldHeight, oldWidth, *data, freq, FFTW_REDFT10, FFTW_REDFT10, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
fftwx_execute(plan);
fftwx_destroy_plan(plan);
if (!preserveSum) for (int i=0; i<oldSize; i++) freq[i] /= 4.0*oldSize; // normalize
// re-organize the frequency bins, for the new image size
Pixel *r; Pixel *w; Pixel *end = freq + newSize;
int minWidth = oldWidth < newWidth ? oldWidth : newWidth;
int minHeight = oldHeight < newHeight ? oldHeight : newHeight;
int diff = newWidth - oldWidth;
if (diff < 0) { // width decrease: truncate each row of frequencies:
r = w = freq;
for (int i=0; i<minHeight; i++) {
for (int j=0; j<newWidth; j++) *(w++) = *(r++);
r += -diff;
}
while (w<end) *(w++)=0;
}
else if (diff > 0) { // width increase: pad each row of frequencies:
r = freq + oldWidth * minHeight;
w = freq + newWidth * minHeight;
for (int i=0; i<minHeight; i++) {
for (int j=0; j<diff; j++) *(--w) = 0;
for (int j=0; j<oldWidth; j++) *(--w) = *(--r);
}
w = freq + newWidth * minHeight;
while (w<end) *(w++)=0;
}
else { // same width: just pad the height if needed:
w = freq + oldSize;
while (w<end) *(w++)=0;
}
// apply a roll-off filter to reduce ringing effects
const int P=3; // a lower P removes more ringing but could be too blurry
if (oldWidth != newWidth) {
int n = minWidth/P;
Pixel d = 1.0/(n+1);
for (int i=0; i<minHeight; i++) {
w = freq + i*newWidth + minWidth - n;
for (int j=n; j>0; j--) {
Pixel y=j*d; y=y*y*(3-2*y);// smoothstep
*(w++) *= y; // fade out 'n' frequency bins
}
}
}
if (oldHeight != newHeight) {
int n = minHeight/P;
Pixel d = 1.0/(n+1);
for (int j=n; j>0; j--) {
w = freq + (minHeight-j)*newWidth;
Pixel y=j*d; y=y*y*(3-2*y); // smoothstep
for (int i=0; i<minWidth; i++) *(w++) *= y;// fade out 'n' frequency bins
}
}
// generate a new image with the truncated/padded/filtered frequency data
plan = fftwx_plan_r2r_2d(newHeight, newWidth, freq, *data, FFTW_REDFT01, FFTW_REDFT01, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
fftwx_execute(plan);
fftwx_destroy_plan(plan);
if (preserveSum) for (int i=0; i<newSize; i++) (*data)[i] /= 4.0*newSize; // normalize
// memory management
free(freq);
if (newSize < oldSize) {
Pixel *nb = realloc(*data, newSize*sizeof(Pixel));
if (nb) *data = nb;
}
return RESULT_OK;
}
int multiply_width(Pixel **data, int width, int height, int scale) {
if (scale < 1) { printf("Scale out of range\n"); return RESULT_ERROR; }
if (scale == 1) { printf("Scale=1, no resizing done.\n"); return RESULT_OK; }
Pixel *nb = realloc(*data, sizeof(Pixel)*width*height*scale);
if (!nb) { printf("Not enough memory for resize\n"); return RESULT_ERROR; }
*data =nb;
printf("Resizing from %dx%d to %dx%d\n", width, height, width*scale, height);
Pixel *r = *data + width*height;
Pixel *w = *data + width*height*scale;
while (w > *data) { --r; for (int i=0; i<scale; i++) *(--w) = *r; }
return RESULT_OK;
}
int divide_width(Pixel **data, int width, int height, int scale) {
if (scale < 1) { printf("Scale out of range\n"); return RESULT_ERROR; }
if (scale == 1) { printf("Scale=1, no resizing done.\n"); return RESULT_OK; }
if (width%scale) { printf("Width not divisible by %d. Use the :: resize operator instead.\n", scale); return RESULT_ERROR; }
printf("Resizing from %dx%d to %dx%d\n", width, height, width/scale, height);
Pixel *r = *data; Pixel *w = *data; Pixel *end = *data + width*height;
while (r < end) {
*w = *(r++);
for (int i=1; i<scale; i++) *w += *(r++);
w++;
}
Pixel *nb = realloc(*data, sizeof(Pixel)*width*height/scale);
if (nb) *data = nb;
return RESULT_OK;
}
int multiply_height(Pixel **data, int width, int height, int scale) {
if (scale < 1) { printf("Scale out of range\n"); return RESULT_ERROR;}
if (scale == 1) { printf("Scale=1, no resizing done.\n"); return RESULT_OK; }
Pixel *nb = realloc(*data, sizeof(Pixel)*width*height*scale);
if (!nb) { printf("Not enough memory for resize\n"); return RESULT_ERROR; }
*data =nb;
printf("Resizing from %dx%d to %dx%d\n", width, height, width, height*scale);
Pixel *r = *data + width*height;
Pixel *w = *data + width*height*scale;
for (int i=0; i<height; i++) {
for (int j=0;;) {
for (int k=0; k<width; k++) *(--w) = *(--r);
if (++j >= scale) break;
r += width;
}
}
return RESULT_OK;
}
int divide_height(Pixel **data, int width, int height, int scale) {
if (scale < 1) { printf("Scale out of range\n"); return RESULT_ERROR; }
if (scale == 1) { printf("Scale=1, no resizing done.\n"); return RESULT_OK; }
if (height%scale) { printf("Height not divisible by %d. Use the :: resize operator instead.\n", scale); return RESULT_ERROR; }
printf("Resizing from %dx%d to %dx%d\n", width, height, width, height/scale);
Pixel *r = *data; Pixel *w = *data; Pixel *end = *data + width*height;
while (r < end) {
for (int i=0; i<width; i++) *(w++) = *(r++);
for (int i=1; i<scale; i++) {
w -= width;
for (int j=0; j<width; j++) *(w++) += *(r++);
}
}
Pixel *nb = realloc(*data, sizeof(Pixel)*width*height/scale);
if (nb) *data = nb;
return RESULT_OK;
}
char *fileExtension(char *filename) {
char *x = filename;
while (*x) x++;
while (--x >= filename) if (*x == '.') return x;
return NULL;
}
int processCommand(char* cmd) { // note: the 'cmd' string is processed destructively.
// main cursor for parsing
char* p = cmd;
// cut off any comments that start with #
while (*p) {
if (*p=='#') { *p=0; break; }
p++;
}
// back to beginning
p = cmd;
// skip whitespace
while (*p && isspace(*p)) p++;
// filter out some cases
if (*p == 0) return RESULT_OK; // empty line
if (*p == '?') { help(); return RESULT_OK; }
if (!isalpha(*p)) {
printf("Syntax error\n");
return RESULT_ERROR;
}
// main cases...
char *left = p; // left side of the command (should be a variable's name and that's all)
while (*p && (isalnum(*p) || *p=='_' || *p=='.')) p++;
char *leftEnd = p;
if (leftEnd - left == 4) {
if (!strncasecmp(left,"done",4)) return RESULT_EXIT;
if (!strncasecmp(left,"quit",4)) return RESULT_EXIT;
if (!strncasecmp(left,"exit",4)) return RESULT_EXIT;
if (!strncasecmp(left,"help",4)) { help(); return RESULT_OK; }
if (!strncasecmp(left,"vars",4)) {
int mem=0;
for (int i=0; i<nVars; i++) {
printf("%s (%dx%d)\n", vars[i].name, vars[i].width, vars[i].height);
mem += vars[i].width * vars[i].height * sizeof(Pixel);
}
if (nVars) printf("Total memory used: %.1lf MB\n", mem*0.000001);
else printf("No variables yet.\n");
return RESULT_OK;
}
}
while (*p && isspace(*p)) p++;
// operator characters
char c0=0, c1=0;
if (*p) c0 = *(p++);
if (*p) c1 = *(p++);
// right side of the command
while (*p && isspace(*p)) p++;
if (!*p) { printf("Right side missing\n"); return RESULT_ERROR; }
char* right = p;
// find trailing whitespace at the end
while (*p) p++;
if (p>cmd) p--;
while (p>cmd && isspace(*p)) p--;
if (*p) p++;
// add string terminators
*p = 0; // for string 'right'
*leftEnd = 0; // for string 'left'
// operator << (load file)
if (c0=='<' && c1=='<') {
char *ext = fileExtension(right);
if (!strncasecmp(ext, ".data-", 6))
{
// case: raw headerless data (type and dimensions are in the file extension)
char *type = ext + 6;
char *w=type; while (*w && *w != '-') w++;
if (!*w || !*(++w)) { printf("File extension is missing dimensions\n"); return RESULT_ERROR; }
char *h = w; while (*h && *h != 'x') h++;
if (!*h || !*(++h)) { printf("File extension is missing dimensions\n"); return RESULT_ERROR; }
int width = atoi(w);
int height = atoi(h);
// XXX: this implementation assumes that the input file has the same endianness as the system architecture (typically little-endian). Maybe we should we devise some other naming standard for headerless data? Perhaps existing notation could indicate little-endian, and some other notation could indicate big-endian? idk
FILE *f = fopen(right, "r");
if (!f) { perror(right); return RESULT_ERROR; }
Var *v = create_var(left, width, height);
if (!v) { fclose(f); return RESULT_ERROR; }
int result = RESULT_OK;
#define LOAD_DATA(T, TNAME) { \
printf("Loading raw headerless "TNAME" data (%dx%d)\n", width, height); \
T *row = malloc(width * sizeof(T)); \
if (row) { \
for (int i=0; i<height; i++) { \
if (fread(row, sizeof(T), width, f) == 0) { printf("Incomplete file\n"); break; } \
Pixel *p = &v->data[i*width]; \
for (int j=0; j<width; j++) p[j] = row[j]; \
} \
free(row); \
} \
else { \
printf("Not enough memory\n"); \
free(v->data); v->data=NULL; v->width=0; v->height=0; \
result = RESULT_ERROR; \
} \
} \
if /**/ (!strncasecmp(type,"float64-",8)) {LOAD_DATA(double, "64-bit floating-point")}
else if (!strncasecmp(type,"float32-",8)) {LOAD_DATA(float, "32-bit floating-point")}
else if (!strncasecmp(type,"uint64-", 7)) {LOAD_DATA(uint64_t,"64-bit unsigned integer")}
else if (!strncasecmp(type,"uint32-", 7)) {LOAD_DATA(uint32_t,"32-bit unsigned integer")}
else if (!strncasecmp(type,"uint16-", 7)) {LOAD_DATA(uint16_t,"16-bit unsigned integer")}
else if (!strncasecmp(type,"uint8-", 6)) {LOAD_DATA(uint8_t, "8-bit unsigned integer")}
else if (!strncasecmp(type,"int64-", 6)) {LOAD_DATA(int64_t, "64-bit signed integer")}
else if (!strncasecmp(type,"int32-", 6)) {LOAD_DATA(int32_t, "32-bit signed integer")}
else if (!strncasecmp(type,"int16-", 6)) {LOAD_DATA(int16_t, "16-bit signed integer")}
else if (!strncasecmp(type,"int8-", 5)) {LOAD_DATA(int8_t, "8-bit signed integer")}
else {
printf("Unrecognized datatype in file extension\n");
free(v->data); v->data=NULL; v->width=0; v->height=0;
result = RESULT_ERROR;
}
fclose(f);
return result; // done loading headerless data
}
// other case: image file:
printf("Loading image file"); fflush(stdout);
int width=0, height=0, nChannels=1;
unsigned char *bytes = stbi_load(right, &width, &height, &nChannels, 0);
if (!bytes) {
perror(" - failed\n");
return RESULT_ERROR;
}
int size = width * height;
int result = RESULT_OK;
if (nChannels == 1) {
printf(" (%dx%d, mono)\n", width, height);
Var *v = create_var(left, width, height);
if (!v) result = RESULT_ERROR;
else {
for (int i=0; i<size; i++) v->data[i] = bytes[i]/255.000;
v->width = width;
v->height = height;
}
}
else if (nChannels <= 4 && nChannels > 1) {
char *ch = "rgba";
printf(" (%dx%d, ", width, height);
for (int i=0; i<nChannels; i++) putchar(ch[i]); printf(")\n");
// generate variables' names such as foo.r foo.g foo.b
int len = strlen(left);
char *vname = malloc(len+3); if(!vname){printf("Out of memory\n");return RESULT_ERROR;}
strcpy(vname, left);
for (int i=0; i<nChannels; i++) {
vname[len+0]='.';
vname[len+1]=ch[i];
vname[len+2]=0;
printf("Loading data into '%s'\n", vname);
Var *v = create_var(vname, width, height);
if (!v) { result = RESULT_ERROR; break; }
for (int j=0; j<size; j++) v->data[j] = bytes[i+j*nChannels]/255.000;
}
free(vname);
}
else printf(" - failed: %d channels not supported\n", nChannels);
stbi_image_free(bytes);
return result;
}
// operator := (copy data)
if (c0==':' && c1=='=') {
// case: right side is a number:
if (isdigit(right[0]) || right[0]=='.' || right[0]=='-' || right[0]=='+') {
Pixel number = atof(right);
Var *lv = find_var(left);
if (!lv) {
printf("Unknown variable '%s'\n", left);
return RESULT_ERROR;
}
printf("Filling '%s' (%dx%d) with value %lf\n", left, lv->width, lv->height, (double)number);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) lv->data[i] = number;
return RESULT_OK;
}
// case: right side is a variable:
Var *rv = find_var(right);
if (!rv) {
printf("Unknown variable: '%s'\n", right);
return RESULT_ERROR;
}
Var *lv = create_var(left, rv->width, rv->height);
if (!lv) return RESULT_ERROR;
printf("Copying '%s' to '%s' (%dx%d)\n", right, left, rv->width, rv->height);
memcpy(lv->data, rv->data, rv->width * rv->height * sizeof(Pixel));
return RESULT_OK;
}
// operators += -= *= /= ^= <= >= _=
if (c1=='=' && (c0=='+'||c0=='-'||c0=='*'||c0=='/'||c0=='^'||c0=='<'||c0=='>'||c0=='_')) {
Var *lv = find_var(left);
if (!lv) {
printf("Unknown variable: %s\n", left);
return RESULT_ERROR;
}
if (!isalpha(right[0])) {
Pixel number = atof(right);
printf("Applying [%c %lf] to '%s' (%dx%d)\n", c0, (double)number, left, lv->width, lv->height);
int size = lv->width * lv->height;
Pixel *data = lv->data;
if (c0=='+') for (int i=0; i<size; i++) data[i] += number;
else if (c0=='-') for (int i=0; i<size; i++) data[i] -= number;
else if (c0=='*') for (int i=0; i<size; i++) data[i] *= number;
else if (c0=='/') for (int i=0; i<size; i++) data[i] /= number;
else if (c0=='^') for (int i=0; i<size; i++) data[i] = pow(data[i], number);
else if (c0=='<') for (int i=0; i<size; i++) data[i] = data[i] < number ? data[i] : number;
else if (c0=='>') for (int i=0; i<size; i++) data[i] = data[i] > number ? data[i] : number;
else if (c0=='_') for (int i=0; i<size; i++) data[i] = IS_NAN(data[i]) ? number : data[i];
return RESULT_OK;
}
Var *rv = find_var(right);
if (!rv) {
printf("Undefined variable: %s\n", right);
return RESULT_ERROR;
}
printf("Applying [%c centered '%s'(%dx%d)] to '%s'(%dx%d)\n", c0, right, rv->width, rv->height, left, lv->width, lv->height);
int width = lv->width < rv->width ? lv->width : rv->width;
int height = lv->height < rv->height ? lv->height : rv->height;
Pixel *lRow = &lv->data[(lv->width-width)/2 + lv->width*((lv->height-height)/2)];
Pixel *rRow = &rv->data[(rv->width-width)/2 + rv->width*((rv->height-height)/2)];
for (int i=0; i<height; i++) {
if (c0=='+') for (int j=0; j<width; j++) lRow[j] += rRow[j];
else if (c0=='-') for (int j=0; j<width; j++) lRow[j] -= rRow[j];
else if (c0=='*') for (int j=0; j<width; j++) lRow[j] *= rRow[j];
else if (c0=='/') for (int j=0; j<width; j++) lRow[j] /= rRow[j];
else if (c0=='^') for (int j=0; j<width; j++) lRow[j] = pow(lRow[j], rRow[j]);
else if (c0=='<') for (int j=0; j<width; j++) lRow[j] = lRow[j] < rRow[j] ? lRow[j] : rRow[j];
else if (c0=='>') for (int j=0; j<width; j++) lRow[j] = lRow[j] > rRow[j] ? lRow[j] : rRow[j];
else if (c0=='_') for (int j=0; j<width; j++) lRow[j] = IS_NAN(lRow[j]) ? rRow[j] : lRow[j];
lRow += lv->width;
rRow += rv->width;
}
return RESULT_OK;
}
// operator [] (array mapping)
if (c0=='[' && c1==']') {
Var *lv = find_var(left);
if (!lv) {
printf("Unknown variable: %s\n", left);
return RESULT_ERROR;
}
Var *rv = find_var(right);
if (!rv) {
printf("Unknown variable: %s\n", right);
return RESULT_ERROR;
}
if (rv->width != 1 && rv->height != 1) {
printf("%s is not a 1-dimensional array\n", right);
return RESULT_ERROR;
}
printf("Replacing the contents of '%s' with corresponding values from array '%s'\n", left, right);
Pixel *p = lv->data;
Pixel *end = &lv->data[lv->width * lv->height];
int n = rv->width * rv->height;
int blanks = 0;
while (p < end) {
if (*p >= 0 && *p < n) *p = rv->data[(int)*p];
else { *p = NAN; blanks++; }
p++;
}
if (blanks) printf(": %d pixels (%d%%) were bad array indexes, so were filled with NaN\n", blanks, (int)(0.5+100.0*blanks/(lv->width*lv->height)));
return RESULT_OK;
}
// operator :: (resize image)
if (c0==':' && c1==':') {
int newWidth=0, newHeight=0, preserveSum=0;
char *rightx = right;
if (right[0]=='$') { // the '$' preserves pixel sums. Note that the syntax was previously '_' instead, in version 0.5
rightx++;
preserveSum=1;
}
Var *rv = find_var(rightx);
if (rv) { newWidth=rv->width; newHeight=rv->height; }
else {
char *p = rightx; // try to parse dimensions from string
while (*p && !isdigit(*p)) p++;
newWidth = atoi(p);
while (*p && isdigit(*p)) p++;
while (*p && !isdigit(*p)) p++;
newHeight = atoi(p);
if (!*p) {
printf("Unknown variable: '%s'\n", rightx);
return RESULT_ERROR;
}
}
Var *lv = find_var(left);
if (newWidth<=0 || newHeight<=0) {
if (lv) printf("Can't scale down '%s' to zero.\nIf you want to clear the data, use: %s @@ clear\n", left, left);
else printf("Can't create variable with 0 in dimensions\n");
return RESULT_ERROR;
}
if (!lv || lv->width<=0 || lv->height<=0) {
lv = create_var(left, newWidth, newHeight);
if (lv==NULL) return RESULT_ERROR;
printf("Creating blank variable '%s' with dimensions %dx%d\n", left, newWidth, newHeight);
memset(lv->data, 0, newWidth*newHeight*sizeof(Pixel));
return RESULT_OK;
}
printf("Resizing '%s'(%dx%d) to ", left, lv->width, lv->height);
if (rv) printf("fit '%s'", rightx);
printf("(%dx%d) - note: may be slow\n", newWidth, newHeight);
if (preserveSum) printf(" + preserving the sum of pixels\n");
int result = resize(&lv->data, lv->width, lv->height, newWidth, newHeight, preserveSum);
if (result==RESULT_OK) { lv->width=newWidth; lv->height=newHeight; }
else printf("Failed (probably out of memory)\n");
return result;
}
// operator >> (save file)
if (c0=='>' && c1=='>') {
int n=0;
Var *lv[4] = {0};
lv[0] = find_var(left);
if (lv[0] && lv[0]->width && lv[0]->height) n = 1;
else {
// try multiple channels - for variable 'foo', look for 'foo.r', 'foo.g', 'foo.b', 'foo.a'
int len = strlen(left);
char *vname = malloc(len+3); if(!vname){printf("Out of memory\n");return RESULT_ERROR;}
strcpy(vname, left);
char *ch = "rgba";
do {
vname[len+0]='.';
vname[len+1]=ch[n];
vname[len+2]=0;
lv[n] = find_var(vname);
} while (lv[n] && lv[n]->data &&
lv[n]->width == lv[0]->width &&
lv[n]->height == lv[0]->height &&
++n < 4);
free(vname);
if (n==0) {
printf("Undefined variable '%s'\n", left);
return RESULT_ERROR;
}
}
int size = lv[0]->width * lv[0]->height;
unsigned char *bytes = malloc(n*size*sizeof(unsigned char));
if (!bytes) {
printf("Not enough memory.\n");
return RESULT_ERROR;
}
if (n==1) printf("'%s'(%dx%d) will generate monochrome file: %s\n", left, lv[0]->width, lv[0]->height, right);
else {
printf("Will generate file %s\nfrom channels", right);
for (int i=0; i<n; i++) printf(" '%s' ", lv[i]->name);
printf(" (%dx%d)\n", lv[0]->width, lv[0]->height);
}
for (int i=0; i<n; i++) {
Pixel *data = lv[i]->data;
int highs=0, lows=0;
for (int j=0; j<size; j++) {
if (data[j] < 0.0) { bytes[j*n+i] = 0; lows++; }
else if (data[j] > 1.0) { bytes[j*n+i] = 255; highs++; }
else { bytes[j*n+i] = data[j]*255.999; }
}
if (highs || lows) {
printf(" Note: '%s' contains overflows(%d pixels are >1)(%d pixels are <0)\n", lv[i]->name, highs, lows);
printf(" Image will not be isochromic.\n If you want it to be, you need to first do:\n");
printf(" %s @@ spread\n (which would alter the contents of '%s').\n", lv[i]->name, lv[i]->name);
}
}
printf("\n Writing %s\n", right);
int wrote=0;
char *ext = fileExtension(right);
if (!strcasecmp(ext,".jpg") || !strcasecmp(ext,".jpeg"))
wrote = stbi_write_jpg(right, lv[0]->width, lv[0]->height, n, bytes, 100);
else wrote = stbi_write_png(right, lv[0]->width, lv[0]->height, n, bytes, lv[0]->width * n);
free(bytes);
if (!wrote) { printf("Can't write file\n"); return RESULT_ERROR; }
return RESULT_OK;
}
// operator @@ (special functions)
if (c0=='@' && c1=='@') {
Var *lv = find_var(left);
if (!lv) {
printf("Unknown variable '%s'\n", left);
return RESULT_ERROR;
}
if (!strcasecmp(right, "spread")) {
return spread(lv->data, lv->data, lv->width, lv->height);
}
if (!strcasecmp(right, "clear") || !strcasecmp(right, "delete") || !strcasecmp(right, "free")) {
printf("Clearing variable '%s'\n", left);
free(lv->data); lv->data = NULL;
lv->width = lv->height = 0;
return RESULT_OK;
}
if (!strcasecmp(right, "stats") || !strcasecmp(right, "statistics")) {
Pixel *data = lv->data;
int size = lv->width * lv->height;
printf("%s\n Dimensions: %d by %d\n", left, lv->width, lv->height);
Pixel sum=0; int count=0;
for (int i=0; i<size; i++) if (!IS_NAN(data[i])) { sum += data[i]; count++; }
printf(" Sum: %lf\n", (double)sum);
Pixel avg=sum/count; Pixel std=0;
printf(" Average (Mean): %lf\n", (double)avg);
for (int i=0; i<size; i++) if (!IS_NAN(data[i])) std += (data[i]-avg)*(data[i]-avg);
std = sqrt(std/count);
printf(" Standard Deviation: (+/-) %lf\n", (double)std);
Pixel lowest=INFINITY; int where=0;
for (int i=0; i<size; i++) if (data[i]<lowest) { lowest=data[i]; where=i; }
printf(" Minimum: %lf at [%d,%d]\n", (double)lowest, where % lv->width, where / lv->width);
Pixel highest=-INFINITY; where=0;
for (int i=0; i<size; i++) if (data[i]>highest) { highest=data[i]; where=i; }
printf(" Maximum: %lf at [%d,%d]\n", (double)highest, where % lv->width, where / lv->width);
return RESULT_OK;
}
if (!strncasecmp(right,"pixel",5)) {
int x,y; double value;
if (sscanf(right+5," [ %d , %d ] = %lf ",&x,&y,&value) != 3) {
printf("pixel: Syntax error\n");
return RESULT_ERROR;
}
if (x < 0 || x >= lv->width || y < 0 || y >= lv->height) {
printf("Pixel location out of range\n");
return RESULT_ERROR;
}
printf("Setting pixel in '%s' at [%d,%d] to value %lf\n",left,x,y,value);
lv->data[x + y*lv->width] = value;
return RESULT_OK;
}
if (!strncasecmp(right,"width",5) || !strncasecmp(right,"$width",6)) {
if (lv->width<=0 || lv->height<=0) {
printf("Can't scale an empty variable\n");
return RESULT_ERROR;
}
char *p = &right[5]; while (*p && *p != '*' && *p != '/') p++;
if (!*p) {
printf("width: missing operand\nSyntax example: if you want to stretch the width by 2: %s @@ width*2", left);
return RESULT_ERROR;
}
Pixel f = atof(p+1); int scale = f;
if (scale != f) {
printf("This scaling method can only multiply or divide by an integer.\n");
printf("Try instead:\n %s :: %dx%d\n", left, (int)(*p=='/' ? 0.5+lv->width/f : 0.5+lv->width*f), lv->height);
return RESULT_ERROR;
}
printf("'%s': ", left); fflush(stdout);
if (*p == '/') {
int result = divide_width(&lv->data, lv->width, lv->height, scale);
if (result != RESULT_OK) return result;
lv->width /= scale;
}
if (*p == '*') {
int result = multiply_width(&lv->data, lv->width, lv->height, scale);
if (result != RESULT_OK) return result;
lv->width *= scale;
}
if (right[0]=='$') printf(" + preserving the sum of pixels\n");
if((right[0]=='$') != (*p=='/')) for (int i=0; i < lv->width*lv->height; i++) lv->data[i] /= scale;
return RESULT_OK;
}
if (!strncasecmp(right,"height",6) || !strncasecmp(right,"$height",7)) {
if (lv->width<=0 || lv->height<=0) {
printf("Can't scale an empty variable\n");
return RESULT_ERROR;
}
char *p = &right[6]; while (*p && *p != '*' && *p != '/') p++;
if (!*p) {
printf("height: missing operand\nSyntax example: if you want to stretch the height by 2: %s @@ height*2", left);
return RESULT_ERROR;
}
Pixel f = atof(p+1); int scale = f;
if (scale != f) {
printf("This scaling method can only multiply or divide by an integer.\n");
printf("Try instead:\n %s :: %dx%d\n", left, lv->width, (int)(*p=='/' ? 0.5+lv->height/f : 0.5+lv->height*f));
return RESULT_ERROR;
}
printf("'%s': ", left); fflush(stdout);
if (*p == '/') {
int result = divide_height(&lv->data, lv->width, lv->height, scale);
if (result != RESULT_OK) return result;
lv->height /= scale;
}
if (*p == '*') {
int result = multiply_height(&lv->data, lv->width, lv->height, scale);
if (result != RESULT_OK) return result;
lv->height *= scale;
}
if (right[0]=='$') printf(" + preserving the sum of pixels\n");
if((right[0]=='$') != (*p=='/')) for (int i=0; i < lv->width*lv->height; i++) lv->data[i] /= scale;
return RESULT_OK;
}
if (!strcasecmp(right, "ln") || !strncasecmp(right, "natural_log", 11)) {
printf("Applying the natural logarithm function, to pixels in '%s'\n", left);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) lv->data[i] = log(lv->data[i]);
return RESULT_OK;
}
if (!strcasecmp(right, "e") || !strcasecmp(right, "exp") || !strcasecmp(right, "exponential")) {
printf("Applying the exponential function (e^x), to pixels in '%s'\n", left);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) lv->data[i] = exp(lv->data[i]);
return RESULT_OK;
}
if (!strcasecmp(right, "neg") || !strcasecmp(right, "negative")) {
printf("Inverting colors in '%s'\n", left);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) lv->data[i] = 1.0 - lv->data[i];
return RESULT_OK;
}
if (!strcasecmp(right, "01") || !strcasecmp(right, "clamp")) {
printf("Clamping numbers in '%s' to range '0 to 1'\n", left);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) {
if (lv->data[i] < 0.0) lv->data[i] = 0.0;
else if (lv->data[i] > 1.0) lv->data[i] = 1.0;
}
return RESULT_OK;
}
if (!strcasecmp(right, "smoothstep") || !strcasecmp(right, "smooth_clamp")) {
printf("Applying smoothstep function (0 to 1) to numbers in '%s'\n", left);
int size = lv->width * lv->height;
for (int i=0; i<size; i++) {
Pixel y = lv->data[i];
if (y <= 0.0) lv->data[i] = 0.0;
else if (y >= 1.0) lv->data[i] = 1.0;
else {
y *= y*(3-2*y);
lv->data[i] = y;
}
}
}
if (!strcasecmp(right, "density_to_quantity") || !strcasecmp(right, "d2q")
|| !strcasecmp(right, "quantity_to_density") || !strcasecmp(right, "q2d"))
{
printf("Treating '%s' like an equirectangular world map\n", left);
int divide = (right[0]=='q');
printf("and %s each pixel by its land area (km^2)", divide?"dividing":"multiplying");
fflush(stdout);
const Pixel EARTH_SURFACE = 510072000; // square kilometers
Pixel increment = M_PI / lv->height;
Pixel firstRow = -M_PI_2 + increment/2;
for (int i=0; i < lv->height; i++) {
Pixel latitude = firstRow + i * increment;
Pixel land = cos(latitude) * EARTH_SURFACE * M_PI_2 / lv->height / lv->width; // land in km^2 per pixel in row
if (divide) land = 1.0 / land;
Pixel *row = &lv->data[i * lv->width];
for (int j=0; j < lv->width; j++) row[j] *= land;
}
printf(" - done\n");
return RESULT_OK;
}
printf("Unknown special function: %s\n", right);
return RESULT_ERROR;
}
if (c0) printf("Unknown operator: %c%c\n", c0, c1);
else printf("Syntax error\n");
return RESULT_ERROR; //
} // end of function processCommand()
void done() {
for (int i=0; i<nVars; i++) {
free(vars[i].name);
free(vars[i].data);
}
nVars=0;
}
#define HISTORY_FILE ".isochromic_history"
#define HISTORY_MAX_SAVED 2000
int main() {
using_history();
read_history(HISTORY_FILE);
printf("Welcome. For help info, type: help\n");
int result = RESULT_OK;
while (result != RESULT_EXIT) {
char *line = readline("() ");
if (!line) break;
if (*line) add_history(line);
result = processCommand(line);
free(line);
}
done();
stifle_history(HISTORY_MAX_SAVED);
write_history(HISTORY_FILE);
return 0;
}