diff --git a/Makefile b/Makefile index c6df7be..2ea66df 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index ed449e5..203a29c 100644 --- a/README.md +++ b/README.md @@ -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 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 Use configuration file for false color generation. -Default: Internal parameters +Default: Internal defaults ``` ## Output diff --git a/temppalette.h b/colorpalette.h similarity index 57% rename from temppalette.h rename to colorpalette.h index 6e6d716..de3d95d 100644 --- a/temppalette.h +++ b/colorpalette.h @@ -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" +}; \ No newline at end of file diff --git a/falsecolor.conf b/falsecolor.conf index cb3c03c..fff1193 100644 --- a/falsecolor.conf +++ b/falsecolor.conf @@ -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 \ No newline at end of file diff --git a/fcolor.c b/fcolor.c index e3608c8..a3a98e1 100644 --- a/fcolor.c +++ b/fcolor.c @@ -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"); -}; - +}; \ No newline at end of file diff --git a/gvipalette.h b/gvipalette.h deleted file mode 100644 index a9ab41f..0000000 --- a/gvipalette.h +++ /dev/null @@ -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" -}; \ No newline at end of file diff --git a/main.c b/main.c index 169c6fe..16138e8 100644 --- a/main.c +++ b/main.c @@ -27,14 +27,15 @@ #include +#include + #include #include #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); diff --git a/offsets.h b/offsets.h index d8980bb..453e4fc 100644 --- a/offsets.h +++ b/offsets.h @@ -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) \ No newline at end of file +#define CONTAINS(str, char) (strchr(str, (int) char) != NULL) +#define MCOMPOSITE(m1, a1, m2, a2) (m1*a1 + m2*a2*(1-a1)) \ No newline at end of file