Browse Source

New false color/color handling

Now RGB based instead of HSV based, this also includes a new RGBA compositing function which cleans up the code responsible false color significantly.

Color palettes have also been moved into 1 file instead of 2
tags/v1.8.0
Xerbo 5 years ago
parent
commit
22b325a5ef
8 changed files with 143 additions and 190 deletions
  1. +2
    -2
      Makefile
  2. +5
    -5
      README.md
  3. +33
    -2
      colorpalette.h
  4. +10
    -9
      falsecolor.conf
  5. +65
    -111
      fcolor.c
  6. +0
    -31
      gvipalette.h
  7. +26
    -29
      main.c
  8. +2
    -1
      offsets.h

+ 2
- 2
Makefile View File

@@ -1,4 +1,4 @@
CC = gcc
CC = clang
BIN = /usr/bin
INCLUDES = -I.
CFLAGS = -O3 -Wall $(INCLUDES)
@@ -7,7 +7,7 @@ OBJS = main.o image.o dsp.o filter.o reg.o fcolor.o
aptdec: $(OBJS)
$(CC) -o $@ $(OBJS) -lm -lsndfile -lpng

main.o: main.c temppalette.h gvipalette.h offsets.h messages.h
main.o: main.c colorpalette.h offsets.h messages.h
dsp.o: dsp.c filtercoeff.h filter.h
filter.o: filter.c filter.h
image.o: image.c satcal.h offsets.h messages.h


+ 5
- 5
README.md View File

@@ -54,7 +54,7 @@ To uninstall
-i [r|a|b|c|t|l]
Output image type
Raw (r), Channel A (a), Channel B (b), False Color (c), Temperature (t), Layered (l)
Default: ab
Default: "ab"

-d <dir>
Images destination directory (optional).
@@ -62,17 +62,17 @@ Default: Current directory

-s [15|16|17|18|19]
Satellite number
For temperature and false color generation
Default: 19
For temperature calibration
Default: "19"

-e [c|t]
Enhancements
Contrast (c) or Crop Telemetry (t)
Defaults: ct
Defaults: "ct"

-c <file>
Use configuration file for false color generation.
Default: Internal parameters
Default: Internal defaults
```

## Output


temppalette.h → colorpalette.h View File

@@ -1,3 +1,34 @@
unsigned char GviPalette[256*3] = {
"\230t\17\233x\22\236{\27\241\200\33\244\203\37\247\210#\252\214'\255\220"
",\260\2240\264\2305\267\2358\272\240=\274\245A\300\251E\303\255I\306\262"
"M\311\266Q\314\272V\317\276Z\322\302^\325\306b\330\312g\334\317k\337\323"
"o\342\330s\344\333w\350\337{\352\344\200\356\350\203\361\354\207\364\360"
"\213\367\364\220\372\370\224\376\376\230\376\375\230\376\372\227\376\366"
"\224\376\362\222\376\357\220\376\352\216\376\347\213\376\343\211\376\340"
"\207\376\334\205\376\330\202\376\325\200\376\321\177\376\315|\376\312z\376"
"\306y\376\302v\376\277t\376\273q\376\267o\376\263m\376\260k\376\254h\376"
"\250f\376\245d\376\241b\376\235_\376\232^\376\226[\376\223Y\376\217W\376"
"\213U\376\207R\376\203Q\376\200N\376}L\376zJ\376vG\376rE\376nB\376k@\376"
"g>\376d<\376`:\376\\7\376X6\376T3\376Q0\376M/\376J-\376F*\376C(\376>%\376"
";$\3767!\3763\37\3760\35\376,\32\376(\31\376$\26\376!\24\376\35\22\376\32"
"\20\376\26\15\376\22\13\376\16\11\376\12\6\376\7\4\376\0\0\373\2\0\366\3"
"\0\362\5\0\355\7\0\350\11\0\343\13\0\336\15\0\332\17\0\325\21\0\320\22\0"
"\313\24\0\307\26\0\303\27\0\275\32\0\270\33\0\264\35\0\257\37\0\253!\0\246"
"#\0\241%\0\234&\0\227)\0\223+\0\216,\0\212.\0\2050\0\2002\0}4\0w6\0s7\0n"
"9\0i;\0e>\0`?\0[A\0VC\0QE\0MG\0HH\0CK\0?M\0:N\0""5P\0""0R\0,S\0'V\0\"X\0"
"\36Z\0\31\\\0\23^\0\17_\0\12a\0\3b\0\6e\2\12i\5\17n\11\23r\14\30v\17\34{"
"\23\40\200\26$\203\31)\207\35.\215\40""1\221$6\225':\232*?\236.C\2431G\250"
"4K\2548O\260;U\265>Y\272A\\\276Eb\303Ie\307Lj\313On\320Ss\324Vw\331Y{\336"
"]\200\342`\203\347d\207\353g\213\357j\217\364n\223\371q\230\376t\222\375"
"{\207\374\205}\373\216r\371\230g\371\241\\\370\254Q\367\265F\365\300;\364"
"\3110\363\323%\362\334\32\361\347\17\357\361\3\356\371\4\353\370\3\347\365"
"\4\342\361\4\335\357\4\331\354\4\323\351\3\317\346\3\313\343\4\306\340\3"
"\301\335\3\275\332\3\270\327\3\263\324\2\257\322\3\252\316\3\245\314\3\241"
"\311\2\234\306\2\230\303\2\223\300\3\216\275\3\211\273\2\205\267\2\200\265"
"\2|\262\2w\257\2s\254\2n\251\2j\246\2e\243\2`\240\2[\235\2W\232\1S\230\2"
"M\225\1I\221\2E\217\1@\214\1;\211\1""7\206\1""1\203\1-\200\0(~\1${\0\37y"
"\0\33u\0\25r\0\21p\0\14l\0\7j\0\3"
};
unsigned char TempPalette[256*3] = {
"\376\376\376\376\376\376\375\375\376\374\375\376\374\375\375\374\373\375"
"\373\373\375\372\373\375\372\373\374\372\372\374\371\372\374\371\371\375"
@@ -38,5 +69,5 @@ unsigned char TempPalette[256*3] = {
"\370J\352\370I\360\370G\367\371F\370\362E\371\354C\370\345A\371\336@\371"
"\326?\372\316=\372\307;\372\277:\372\2679\373\2577\373\2475\373\2373\373"
"\2261\373\2161\373\206.\373}-\374u,\375k*\374b(\375Y'\375O%\375E#\375;\""
"\3762\40\376(\37\376\35\35",
};
"\3762\40\376(\37\376\35"
};

+ 10
- 9
falsecolor.conf View File

@@ -1,9 +1,10 @@
30.0
90.0
100.0
230 0.2 0.3
230 0.0 1.0
200.0 0.7 0.6
240.0 0.6 0.4
60.0 0.6 0.2
100.0 0.1 0.5
28 44 95
23 78 37
240 250 255
50
0.5
24
34
14
141
114

+ 65
- 111
fcolor.c View File

@@ -3,133 +3,89 @@
#include "offsets.h"

typedef struct {
float h, s, v;
} hsvpix_t;

static void HSVtoRGB(float *r, float *g, float *b, hsvpix_t pix) {
int i;
double f, p, q, t, h;

if (pix.s == 0) {
// achromatic (grey)
*r = *g = *b = pix.v;
return;
}

h = pix.h / 60; // sector 0 to 5
i = floor(h);
f = h - i; // factorial part of h
p = pix.v * (1 - pix.s);
q = pix.v * (1 - pix.s * f);
t = pix.v * (1 - pix.s * (1 - f));

switch (i) {
case 0:
*r = pix.v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = pix.v;
*b = p;
break;
case 2:
*r = p;
*g = pix.v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = pix.v;
break;
case 4:
*r = t;
*g = p;
*b = pix.v;
break;
default: // case 5:
*r = pix.v;
*g = p;
*b = q;
break;
}

}
float r, g, b, a;
} rgba;

static struct {
float Seathreshold;
float Landthreshold;
float Threshold;
hsvpix_t CloudTop;
hsvpix_t CloudBot;
hsvpix_t SeaTop;
hsvpix_t SeaBot;
hsvpix_t GroundTop;
hsvpix_t GroundBot;
rgba Sea;
rgba Land;
rgba Cloud;
int Seaintensity;
float Seaoffset;
int Landthreshold;
int Landintensity;
int Landoffset;
int Cloudthreshold;
int Cloudintensity;
} fcinfo = {
30.0, 90.0, 155.0, {
230, 0.2, 0.3}, {
230, 0.0, 1.0}, {
200.0, 0.7, 0.6}, {
240.0, 0.6, 0.4}, {
60.0, 0.6, 0.2}, {
100.0, 0.0, 0.5}
{28, 44, 95, 1},
{23, 78, 37, 1},
{240, 250, 255, 1},
50,
0.5,
24,
34,
14,
141,
114
};

void readfconf(char *file) {
// Read the config file
void readfcconf(char *file) {
FILE *fin;

fin = fopen(file, "r");
if (fin == NULL)
return;

fscanf(fin, "%g\n", &fcinfo.Seathreshold);
fscanf(fin, "%g\n", &fcinfo.Landthreshold);
fscanf(fin, "%g\n", &fcinfo.Threshold);
fscanf(fin, "%g %g %g\n", &fcinfo.CloudTop.h, &fcinfo.CloudTop.s, &fcinfo.CloudTop.v);
fscanf(fin, "%g %g %g\n", &fcinfo.CloudBot.h, &fcinfo.CloudBot.s, &fcinfo.CloudBot.v);
fscanf(fin, "%g %g %g\n", &fcinfo.SeaTop.h, &fcinfo.SeaTop.s, &fcinfo.SeaTop.v);
fscanf(fin, "%g %g %g\n", &fcinfo.SeaBot.h, &fcinfo.SeaBot.s, &fcinfo.SeaBot.v);
fscanf(fin, "%g %g %g\n", &fcinfo.GroundTop.h, &fcinfo.GroundTop.s, &fcinfo.GroundTop.v);
fscanf(fin, "%g %g %g\n", &fcinfo.GroundBot.h, &fcinfo.GroundBot.s, &fcinfo.GroundBot.v);
fscanf(fin, "%g %g %g\n", &fcinfo.Sea.r, &fcinfo.Sea.g, &fcinfo.Sea.b);
fscanf(fin, "%g %g %g\n", &fcinfo.Land.r, &fcinfo.Land.g, &fcinfo.Land.b);
fscanf(fin, "%g %g %g\n", &fcinfo.Cloud.r, &fcinfo.Cloud.g, &fcinfo.Cloud.b);
fscanf(fin, "%d\n", &fcinfo.Seaintensity);
fscanf(fin, "%g\n", &fcinfo.Seaoffset);
fscanf(fin, "%d\n", &fcinfo.Landthreshold);
fscanf(fin, "%d\n", &fcinfo.Landintensity);
fscanf(fin, "%d\n", &fcinfo.Landoffset);
fscanf(fin, "%d\n", &fcinfo.Cloudthreshold);
fscanf(fin, "%d", &fcinfo.Cloudintensity);

fclose(fin);
};

void falsecolor(double v, double t, float *r, float *g, float *b) {
hsvpix_t top, bot, c;
double scv, sct;

if (t > fcinfo.Threshold) {
if (v < fcinfo.Seathreshold) {
// Sea
top = fcinfo.SeaTop, bot = fcinfo.SeaBot;
scv = v / fcinfo.Seathreshold;
sct = (256.0 - t) / (256.0 - fcinfo.Threshold);
} else {
// Ground
top = fcinfo.GroundTop, bot = fcinfo.GroundBot;
scv = (v - fcinfo.Seathreshold) / (fcinfo.Landthreshold - fcinfo.Seathreshold);
sct = (256.0 - t) / (256.0 - fcinfo.Threshold);
}
} else {
// Clouds
top = fcinfo.CloudTop, bot = fcinfo.CloudBot;
scv = v / 256.0;
sct = (256.0 - t) / 256.0;
}
// RGBA Composite
rgba composite(rgba top, rgba bottom){
rgba composite;
composite.r = MCOMPOSITE(top.r, top.a, bottom.r, bottom.a);
composite.g = MCOMPOSITE(top.g, top.a, bottom.g, bottom.a);
composite.b = MCOMPOSITE(top.b, top.a, bottom.b, bottom.a);
composite.a = bottom.a == 1 || top.a == 1 ? 1 : (top.a+bottom.a)/2;
return composite;
}

c.s = top.s + sct * (bot.s - top.s);
c.v = top.v + scv * (bot.v - top.v);
c.h = top.h + scv * sct * (bot.h - top.h);
void falsecolor(float vis, float temp, float *r, float *g, float *b){
rgba buffer;
fcinfo.Land.a = 0; fcinfo.Sea.a = 0;

HSVtoRGB(r, g, b, c);
};
// Calculate intensity of sea and land
fcinfo.Sea.a = CLIP(vis, 0, 20)/fcinfo.Seaintensity + fcinfo.Seaoffset;
if(vis > fcinfo.Landthreshold) fcinfo.Land.a = CLIP(vis-fcinfo.Landoffset, 0, fcinfo.Landintensity)/fcinfo.Landintensity;

// Composite land on top of sea
buffer = composite(fcinfo.Land, fcinfo.Sea);
buffer.a = 1;

// Composite clouds on top
fcinfo.Cloud.a = CLIP(temp-fcinfo.Cloudthreshold, 0, fcinfo.Cloudintensity)/fcinfo.Cloudintensity;
buffer = composite(fcinfo.Cloud, buffer);

*r = buffer.r;
*g = buffer.g;
*b = buffer.b;
}

// GVI (global vegetation index) false color
void Ngvi(float **prow, int nrow) {
printf("GVI... ");
printf("Computing GVI false color\n");
fflush(stdout);

for (int n = 0; n < nrow; n++) {
@@ -148,6 +104,4 @@ void Ngvi(float **prow, int nrow) {
pixelv[i + CHB_OFFSET] = pv;
}
}
printf("Done\n");
};

};

+ 0
- 31
gvipalette.h View File

@@ -1,31 +0,0 @@
unsigned char GviPalette[256*3] = {
"\230t\17\233x\22\236{\27\241\200\33\244\203\37\247\210#\252\214'\255\220"
",\260\2240\264\2305\267\2358\272\240=\274\245A\300\251E\303\255I\306\262"
"M\311\266Q\314\272V\317\276Z\322\302^\325\306b\330\312g\334\317k\337\323"
"o\342\330s\344\333w\350\337{\352\344\200\356\350\203\361\354\207\364\360"
"\213\367\364\220\372\370\224\376\376\230\376\375\230\376\372\227\376\366"
"\224\376\362\222\376\357\220\376\352\216\376\347\213\376\343\211\376\340"
"\207\376\334\205\376\330\202\376\325\200\376\321\177\376\315|\376\312z\376"
"\306y\376\302v\376\277t\376\273q\376\267o\376\263m\376\260k\376\254h\376"
"\250f\376\245d\376\241b\376\235_\376\232^\376\226[\376\223Y\376\217W\376"
"\213U\376\207R\376\203Q\376\200N\376}L\376zJ\376vG\376rE\376nB\376k@\376"
"g>\376d<\376`:\376\\7\376X6\376T3\376Q0\376M/\376J-\376F*\376C(\376>%\376"
";$\3767!\3763\37\3760\35\376,\32\376(\31\376$\26\376!\24\376\35\22\376\32"
"\20\376\26\15\376\22\13\376\16\11\376\12\6\376\7\4\376\0\0\373\2\0\366\3"
"\0\362\5\0\355\7\0\350\11\0\343\13\0\336\15\0\332\17\0\325\21\0\320\22\0"
"\313\24\0\307\26\0\303\27\0\275\32\0\270\33\0\264\35\0\257\37\0\253!\0\246"
"#\0\241%\0\234&\0\227)\0\223+\0\216,\0\212.\0\2050\0\2002\0}4\0w6\0s7\0n"
"9\0i;\0e>\0`?\0[A\0VC\0QE\0MG\0HH\0CK\0?M\0:N\0""5P\0""0R\0,S\0'V\0\"X\0"
"\36Z\0\31\\\0\23^\0\17_\0\12a\0\3b\0\6e\2\12i\5\17n\11\23r\14\30v\17\34{"
"\23\40\200\26$\203\31)\207\35.\215\40""1\221$6\225':\232*?\236.C\2431G\250"
"4K\2548O\260;U\265>Y\272A\\\276Eb\303Ie\307Lj\313On\320Ss\324Vw\331Y{\336"
"]\200\342`\203\347d\207\353g\213\357j\217\364n\223\371q\230\376t\222\375"
"{\207\374\205}\373\216r\371\230g\371\241\\\370\254Q\367\265F\365\300;\364"
"\3110\363\323%\362\334\32\361\347\17\357\361\3\356\371\4\353\370\3\347\365"
"\4\342\361\4\335\357\4\331\354\4\323\351\3\317\346\3\313\343\4\306\340\3"
"\301\335\3\275\332\3\270\327\3\263\324\2\257\322\3\252\316\3\245\314\3\241"
"\311\2\234\306\2\230\303\2\223\300\3\216\275\3\211\273\2\205\267\2\200\265"
"\2|\262\2w\257\2s\254\2n\251\2j\246\2e\243\2`\240\2[\235\2W\232\1S\230\2"
"M\225\1I\221\2E\217\1@\214\1;\211\1""7\206\1""1\203\1-\200\0(~\1${\0\37y"
"\0\33u\0\25r\0\21p\0\14l\0\7j\0\3g"
};

+ 26
- 29
main.c View File

@@ -27,14 +27,15 @@

#include <libgen.h>

#include <math.h>

#include <sndfile.h>
#include <png.h>

#include "messages.h"
#include "offsets.h"

#include "temppalette.h"
#include "gvipalette.h"
#include "colorpalette.h"

extern int getpixelrow(float *pixelv);
extern int init_dsp(double F);;
@@ -74,7 +75,7 @@ static int initsnd(char *filename) {

// Get a sample from the wave file
int getsample(float *sample, int nb) {
return(sf_read_float(inwav, sample, nb));
return sf_read_float(inwav, sample, nb);
}

static png_text text_ptr[] = {
@@ -95,7 +96,7 @@ static int ImageOut(char *filename, char *chid, float **prow, int nrow, int widt
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
fprintf(stderr, ERR_PNG_WRITE);
return(1);
return(0);
}

// Metadata
@@ -103,7 +104,7 @@ static int ImageOut(char *filename, char *chid, float **prow, int nrow, int widt
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
fprintf(stderr, ERR_PNG_INFO);
return(1);
return(0);
}

if(palette == NULL) {
@@ -156,9 +157,9 @@ static int ImageOut(char *filename, char *chid, float **prow, int nrow, int widt
}

if(layered){
// Layered image, basically overlay highlights in channel B over channel A
float cloud = CLIP(pixelv[i+CHB_OFFSET]-141, 0, 255)/114*255;
pixel[i] = CLIP(pixelv[i+CHA_OFFSET] + cloud, 0, 255);
// Layered image, overlay highlights in channel B over channel A
float cloud = CLIP(pixelv[i+CHB_OFFSET]-141, 0, 255)/114;
pixel[i] = MCOMPOSITE(255, cloud, pixelv[i+CHA_OFFSET], 1);
}else{
pixel[i] = pixelv[i + offset + f];
}
@@ -170,7 +171,7 @@ static int ImageOut(char *filename, char *chid, float **prow, int nrow, int widt
fclose(pngfile);
printf("\nDone\n");
png_destroy_write_struct(&png_ptr, &info_ptr);
return(0);
return(1);
}

static int ImageRGBOut(char *filename, float **prow, int nrow) {
@@ -178,7 +179,7 @@ static int ImageRGBOut(char *filename, float **prow, int nrow) {
png_infop info_ptr;
png_structp png_ptr;

extern void falsecolor(double v, double t, float *r, float *g, float *b);
extern void falsecolor(float vis, float temp, float *r, float *g, float *b);

// Initalise the PNG writer
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
@@ -204,7 +205,7 @@ static int ImageRGBOut(char *filename, float **prow, int nrow) {
text_ptr[1].text_length = strlen(text_ptr[1].text);
png_set_text(png_ptr, info_ptr, text_ptr, 3);

printf("Computing false color & writing: %s... ", filename);
printf("Computing false color & writing: %s", filename);
fflush(stdout);
pngfile = fopen(filename, "wb");
if (pngfile == NULL) {
@@ -221,23 +222,20 @@ static int ImageRGBOut(char *filename, float **prow, int nrow) {
pixelc = prow[n];

for (int i = 0; i < CH_WIDTH - 1; i++) {
float v, t;
float r, g, b;

v = pixelc[i+CHA_OFFSET];
t = pixelc[i+CHB_OFFSET];
float r = 0, g = 0, b = 0;

falsecolor(v, t, &r, &g, &b);
// False color computation
falsecolor(pixelc[i+CHA_OFFSET], pixelc[i+CHB_OFFSET], &r, &g, &b);

pix[i].red = 255.0 * r;
pix[i].green = 255.0 * g;
pix[i].blue = 255.0 * b;
pix[i].red = r;
pix[i].green = g;
pix[i].blue = b;
}
png_write_row(png_ptr, (png_bytep) pix);
}
png_write_end(png_ptr, info_ptr);
fclose(pngfile);
printf("Done\n");
printf("\nDone\n");
png_destroy_write_struct(&png_ptr, &info_ptr);
return(0);
}
@@ -281,7 +279,7 @@ static void Distrib(char *filename, float **prow, int nrow) {
extern int calibrate(float **prow, int nrow, int offset, int contrastBoost);
extern void Temperature(float **prow, int nrow, int ch, int offset);
extern int Ngvi(float **prow, int nrow);
extern void readfconf(char *file);
extern void readfcconf(char *file);
extern int optind;
extern char *optarg;

@@ -343,7 +341,7 @@ int main(int argc, char **argv) {
break;
// False color config file
case 'c':
readfconf(optarg);
readfcconf(optarg);
break;
// Output image type
case 'i':
@@ -366,7 +364,6 @@ int main(int argc, char **argv) {
usage();
}
}

if(optind == argc){
printf("No audio files provided.\n");
usage();
@@ -419,7 +416,7 @@ int main(int argc, char **argv) {
sf_close(inwav);

// Layered images require contrast enhancements
int contrastBoost = CONTAINS(enchancements, 'c') || CONTAINS(imgopt, 'l');
int contrastBoost = CONTAINS(enchancements, 'c') || CONTAINS(imgopt, 'l') || CONTAINS(imgopt, 'c');

chA = calibrate(prow, nrow, CHA_OFFSET, 0);
chB = calibrate(prow, nrow, CHB_OFFSET, 0);
@@ -433,8 +430,8 @@ int main(int argc, char **argv) {
ImageOut(pngfilename, "Temperature", prow, nrow, CH_WIDTH, CHB_OFFSET, (png_color*)TempPalette, 0, 0);
}

// We have to run the contrast enhance here because the temperature function requires real data
// Yes, this is bodgy, yes I should replace this with something more elegant
// We have to run the contrast enhance here because the temperature calibration requires real data
// Yes, this requires running a large chunk of code again, but this will be addressed in the future
chA = calibrate(prow, nrow, CHA_OFFSET, contrastBoost);
chB = calibrate(prow, nrow, CHB_OFFSET, contrastBoost);

@@ -480,10 +477,10 @@ int main(int argc, char **argv) {

// False color image
if(CONTAINS(imgopt, 'c')){
if (chA == 2 && chB == 4) { // Normal false color
if (chA == 2 && chB >= 4) { // Normal false color
sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
ImageRGBOut(pngfilename, prow, nrow);
} else if (chA == 1 && chB == 2) { // GVI false color
} else if (chA == 1 && chB == 2) { // GVI (global vegetation index) false color
Ngvi(prow, nrow);
sprintf(pngfilename, "%s/%s-c.png", pngdirname, name);
ImageOut(pngfilename, "GVI False Color", prow, nrow, CH_WIDTH, CHB_OFFSET, (png_color*)GviPalette, 0, 0);


+ 2
- 1
offsets.h View File

@@ -9,4 +9,5 @@
#define TOTAL_TELE (SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH+SYNC_WIDTH+SPC_WIDTH+TELE_WIDTH)

#define CLIP(val, bottom, top) (val > top ? top : (val > bottom ? val : bottom))
#define CONTAINS(str, char) (strchr(str, (int) char) != NULL)
#define CONTAINS(str, char) (strchr(str, (int) char) != NULL)
#define MCOMPOSITE(m1, a1, m2, a2) (m1*a1 + m2*a2*(1-a1))

Loading…
Cancel
Save