/* determine average RGB of image then find closest matches in database _ _ _ (_)_ __ __ _(_) __| | ___ | | '_ \ / _` | |/ _` | / __| | | |_) | (_| | | (_| | _ | (__ _/ | .__/ \__, |_|\__,_| (_) \___| |__/|_| |___/ identifies the color of an insulator. calculates the average RGB of an image, then compares it to a database of insulator images, finding the 5 closest matches. the insulator image to be identified must have a white background all the way around. if it's sitting on something, the something's color will influence the results. see jpgmean.c for details about how the average RGB is calculated. ian macky feb 2004 */ #include #include #include #include #include #include "jpgmean.h" #define BEST_N 5 /* show this many matches */ #define BEST_THRESH 50.0 /* ignore matches if farther */ #define MAX_PATH 512 #define MAX_LINE 1024 #define CMPLIST "../cmplist" /* list of comparable images */ #define HEADER "jpgid.head" #define TAILER "jpgid.tail" #define NO_MATCHES "NO MATCHES" /* === GLOBALS =========================================================== */ int debug; char *root; char *image, imagebuf[MAX_PATH]; char *ofile, ofilebuf[MAX_PATH], ofilename[MAX_PATH]; double white_dist, black_dist, standard_units; JSAMPLE mean_red, mean_green, mean_blue; JSAMPLE joutrow[MAX_IMAGE_WIDTH * 3]; /* R G B for each pixel */ JSAMPROW joutarray[1] = { joutrow }; JSAMPLE jinrow[MAX_IMAGE_WIDTH * 3]; JSAMPROW jinarray[1] = { jinrow }; /* best color matches */ typedef struct { char path[MAX_PATH]; unsigned r, g, b; double distance; char colorname[MAX_PATH]; } match; match matches[BEST_N]; int n_matches; int n_thresh; /* === FUNCTION DECLARATIONS ============================================= */ char *best_color(void); boolean cat_file(char *file); void copy_data(char *dest, FILE *f, char *boundry); void generate_html(unsigned width, unsigned height); boolean parse_args(int argc, char *argv[]); boolean parse_query(void); boolean process_header(char *name); boolean usage(char *s); /* === MAIN ============================================================== */ int main(int argc, char **argv) { char *qs; char *error_string; unsigned width, height; white_dist = black_dist = standard_units = -1; /* -1 = auto */ ofile = NULL; /* if environment variable QUERY_STRING is defined, we're running as CGI */ if ((qs = getenv("QUERY_STRING"))) /* envar defined? */ { puts("Content-type: text/html\n"); /* we'll return this */ puts("Insulator Color Identifier"); puts(""); if (!parse_query()) return 1; } else if (!parse_args(argc, argv)) return 1; /* --------------------------------------------------------------------- */ /* figure average color of image */ if (!jpg_mean(image, ofile, &error_string, white_dist, black_dist, standard_units, &mean_red, &mean_green, &mean_blue, &width, &height, (debug > 1))) { if (qs) printf("

%s

\n", error_string); else printf("Couldn't figure mean RGB of image '%s'", image); return 1; } /* for HTML output, start with boilerplate header */ if (qs) generate_html(width, height); else /* else just output the average RGB as #FFFFFF */ printf("%02X%02X%02X\n", (unsigned) mean_red, (unsigned) mean_green, (unsigned) mean_blue); return 0; } boolean usage(char *s) { if (s) puts(s); puts("usage: jpgid [switches] "); puts("switches:"); puts(" -b "); puts(" -w "); puts(" -s "); puts(" -o "); return FALSE; } boolean parse_args(int argc, char *argv[]) { char *arg, *switches; int sw; /* process switches */ for (argv++, argc--; (arg = *argv) && (*arg == '-'); argv++, argc--) { switches = arg + 1; while ((sw = *switches++)) { switch (sw) { case 'b': if (!argc) return usage("missing black_distance (0-255) after -b"); black_dist = atof(*++argv); argc--; break; case 'o': if (!argc) return usage("missing output filename after -o"); ofile = *++argv; argc--; break; case 's': if (!argc) return usage("missing standard units after -s"); standard_units = atof(*++argv); argc--; break; case 'w': if (!argc) return usage("missing white_distance (0-255) after -w"); white_dist = atof(*++argv); argc--; break; case 'x': debug++; break; default: return usage("unknown switch"); } } } if (!argc) return usage(NULL); image = *argv; return TRUE; } boolean cat_file(char *file) { FILE *f; char line[MAX_LINE]; if (!(f = fopen(file, "r"))) { printf("file '%s' is supposed to be here
\n", file); return FALSE; } while (fgets(line, sizeof(line), f)) fputs(line, stdout); fclose(f); return TRUE; } void generate_html(unsigned width, unsigned height) { match *m; char *e, *colorname; char thumb[MAX_PATH]; int i; cat_file(HEADER); puts("
"); printf("Shows %d closest matches " "to your image and their color names.
", BEST_N); puts("Images should use 5000°K" " lighting and have a full white background
"); puts("How's it work? Read the source: " "jpgid.c and " "jpgmean.c

"); puts(""); puts("
"); puts(""); /* * * * * * * */ puts(""); /* * * * * * * */ puts(""); /* * * * * * * */ puts("\n
"); puts("White Distance:
"); puts("(0-444, default 50)
"); if (white_dist < 0) puts("

"); else printf("

", white_dist); puts("Black Distance:
"); puts("(0-255, default 50)
"); if (black_dist < 0) puts("

"); else printf("

", black_dist); puts("Standard Units:
"); puts("(0-Infinity, default 2)
"); if (standard_units < 0) puts("

"); else printf("

", standard_units); puts("
"); printf("

", ofilename, width, height); if (!(colorname = best_color())) colorname = NO_MATCHES; puts(""); puts("\n", colorname); puts("
"); printf("%s
"); printf("Average RGB of selected pixels (%u,%u,%u) or #%02X%02X%02X
", (unsigned) mean_red, (unsigned) mean_green, (unsigned) mean_blue, (unsigned) mean_red, (unsigned) mean_green, (unsigned) mean_blue); puts("
"); if (n_matches == 1) puts("1 match
"); else if (n_matches < n_thresh) printf("Best %d of %d
\n", n_matches, n_thresh); else printf("%d matches
\n", n_matches); printf("(distance < %g)

\n", BEST_THRESH); for (i = 0; i < n_matches; i++) { m = &matches[i]; if (i) puts("\n
\n"); strcpy(thumb, m->path); e = thumb + strlen(thumb) - 1; while ((e >= thumb) && (*e != '.')) e--; strcpy(e, "tt.jpg"); printf("
", m->path, thumb, m->colorname); printf("%s
", m->colorname); printf("%.3g
\n", m->distance); } puts("
"); puts("
"); cat_file(TAILER); } #define CONT_DISP "Content-Disposition: form-data;" boolean parse_query(void) { FILE *out; char boundry[MAX_LINE], line[MAX_LINE], name[MAX_LINE]; int c; root = getenv("SITE_HTMLROOT"); if (!fgets(boundry, sizeof(boundry), stdin)) return FALSE; /* for each section */ for (;;) { if (!process_header(name)) { puts("header processing failed
\n"); return FALSE; } if (!strcmp(name, "file")) { image = imagebuf; if (root) strcpy(image, root); else *image = 0; strcat(image, "../tmp/"); strcat(image, ofilename); ofile = ofilebuf; if (root) strcpy(ofile, root); else *ofile = 0; strcat(ofile, "../tmp/cmp_"); strcat(ofile, ofilename); if (!(out = fopen(image, "wb"))) { printf("failed to output image '%s'
\n", image); return FALSE; } copy_data(NULL, out, boundry); fclose(out); } else { copy_data(line, NULL, boundry); if (!strcmp(name, "white")) { if (*line != '\r') white_dist = atof(line); } else if (!strcmp(name, "black")) { if (*line != '\r') black_dist = atof(line); } else if (!strcmp(name, "su")) { if (*line != '\r') standard_units = atof(line); } else { printf("unknown keyword '%s'
\n", name); return FALSE; } } if ((c = getchar()) == EOF) break; else ungetc(c, stdin); } return TRUE; } /* process header */ boolean process_header(char *name) { char *colon, *semi, *key, *eq, *value, *dest, *e; char line[MAX_LINE], save_line[MAX_LINE]; char path[MAX_PATH]; int c; /* for each line of header */ for (;;) { if (!fgets(line, sizeof(line), stdin)) { puts("EOF reading header
"); return FALSE; } strcpy(save_line, line); if (*line == '\r') /* blank line... */ break; /* ...ends header. */ if (!(colon = strchr(line, ':'))) { puts("header line missing colon:
"); goto header_fail; } *colon++ = 0; /* skip whitespace */ for (value = colon; isspace(*value); value++) ; if (!strcmp(line, "Content-Disposition")) { if (!(semi = strchr(value, ';'))) { puts("header line key not Content-Disposition:
"); goto header_fail; } *semi = 0; if (strcmp(value, "form-data")) { puts("header line value not form-data:
\n"); goto header_fail; } /* for each field */ for (key = semi + 1; key; key = semi) { if ((semi = strchr(key, ';'))) /* another follows? */ *semi++ = 0; /* tie it off & flag */ while (isspace(*key)) /* skip whitespace */ key++; /* name="value" */ if (!(eq = strchr(key, '='))) { puts("header line field missing equals sign:\n"); goto header_fail; } *eq = 0; value = eq + 1; if (!strcmp(key, "name")) dest = name; else if (!strcmp(key, "filename")) dest = path; else { printf("unknown header line key '%s':
\n", key); goto header_fail; } if (*value != '"') { puts("header field value not quoted:
"); goto header_fail; } while ((c = *++value) != '"') *dest++ = c; *dest = 0; /* for filenames, strip the path */ if (!strcmp(key, "filename")) { e = path + strlen(path) - 1; while ((e >= path) && (*e != '/') && (*e != '\\') && (*e != ':')) e--; strcpy(ofilename, e + 1); } } } else if (!strcmp(line, "Content-Type")) { if (strcmp(value, "image/jpeg\r\n") && /* must be jpeg */ strcmp(value, "image/pjpeg\r\n")) /* or pjpeg */ { puts("header content-type not image/jpeg:
\n"); goto header_fail; } } else { puts("unknown header line:
"); goto header_fail; } } return TRUE; header_fail: puts(save_line); return FALSE; } void copy_data(char *dest_buf, FILE *dest_f, char *boundry) { char *lock; int c, l, n_skipped; while ((c = getchar()) != EOF) { if (c == *boundry) /* potential start of boundry? */ { lock = boundry + 1; while ((l = *lock++)) if ((c = getchar()) != l) break; if (!l) /* picked lock? */ break; /* yes! boundry matched. */ /* no, need to insert chars we just skipped over */ n_skipped = lock - boundry - 1; if (dest_buf) { memcpy(dest_buf, boundry, n_skipped); dest_buf += n_skipped; } else fwrite(boundry, 1, n_skipped, dest_f); } if (dest_buf) *dest_buf++ = c; else putc(c, dest_f); } if (dest_buf) *dest_buf = 0; } char *best_color() { FILE *f; match *m; char line[MAX_LINE], path[MAX_PATH], colorname[MAX_LINE]; unsigned r, g, b; double dist; int n, i, n_rest; if (!(f = fopen(CMPLIST, "r"))) { printf("Failed to open cmplist '%s'\n", CMPLIST); return NULL; } while (fgets(line, sizeof(line), f)) { n = sscanf(line, "%s %2X%2X%2X %[^\n]\n", path, &r, &g, &b, colorname); if (n != 5) { printf("Bad cmplist line (n=%d): '%s'\n", n, CMPLIST); return NULL; } dist = rgb_distance(mean_red, mean_green, mean_blue, r, g, b); if (dist > BEST_THRESH) continue; /* not even in the running */ n_thresh++; /* bump # within threshhold */ /* see if it's a new best */ for (i = 0; i < n_matches; i++) if (dist < matches[i].distance) break; /* yes, new closer distance */ if ((n_matches < BEST_N) || (i < n_matches)) { m = &matches[i]; /* we're new guy here */ /* move rest of them down */ if ((n_rest = n_matches - i) > 0) { if (i + n_rest >= BEST_N) n_rest--; memmove(m + 1, m, n_rest * sizeof(match)); } /* stick us in */ strcpy(m->path, path); m->r = r; m->g = g; m->g = b; m->distance = dist; strcpy(m->colorname, colorname); if (n_matches < BEST_N) /* keep to max of BEST_N */ n_matches++; } } fclose(f); return n_matches ? matches->colorname : NULL; } /* $Id: jpgid.c,v 1.13 2005/03/14 00:40:54 ian Exp $ */