/***************************************************************************** * Copyright (c) 1999-2000 Alan DeKok * * All rights reserved. * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, advertising * materials, and other materials related to such distribution and use, * acknowledge that the software was developed by Alan DeKok. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * $Id: isreal.c,v 1.14 2000/01/19 18:55:31 aland Exp $ * *****************************************************************************/ #include #include #include #include #include #include #ifndef FALSE #define FALSE (0) #define TRUE (!FALSE) #endif enum { REAL = 0, REALISTIC_2, REALISTIC, UNREAL, }; /* * These numbers define the boundaries at which a picture * is determined to be real, realistic, or unreal. Play with them * carefully... */ #define RAW_VARIANCE_UNREAL (0.75) #define RAW_VARIANCE_REALISTIC (0.25) /* * Ray-traced images seem to come in with variance of >75 or so. */ #define LOG_VARIANCE_REALISTIC (25.) #define LOG_VARIANCE_REALISTIC_2 (75.) /* * Default value for the boundary block size. */ static int boundary = 1024; /* * By default don't wipe the blocks, just darken them. */ static int wipe = FALSE; static char *pixelfile = NULL; static char *foldedfile = NULL; /* * This array is a list of what the folded statistics numbers * SHOULD be. */ static const double folded[256] = { 2.000000, 1.000000, 0.584963, 0.415037, 0.321928, 0.263034, 0.222392, 0.192645, 0.169925, 0.152003, 0.137504, 0.125531, 0.115477, 0.106915, 0.099536, 0.093109, 0.087463, 0.082462, 0.078003, 0.074001, 0.070389, 0.067114, 0.064130, 0.061401, 0.058894, 0.056584, 0.054448, 0.052467, 0.050626, 0.048910, 0.047306, 0.045804, 0.044394, 0.043069, 0.041820, 0.040642, 0.039528, 0.038474, 0.037475, 0.036526, 0.035624, 0.034765, 0.033947, 0.033167, 0.032421, 0.031709, 0.031027, 0.030374, 0.029747, 0.029146, 0.028569, 0.028014, 0.027481, 0.026967, 0.026472, 0.025995, 0.025535, 0.025091, 0.024662, 0.024248, 0.023847, 0.023459, 0.023084, 0.022720, 0.022368, 0.022026, 0.021695, 0.021374, 0.021062, 0.020759, 0.020464, 0.020178, 0.019900, 0.019629, 0.019365, 0.019109, 0.018859, 0.018616, 0.018379, 0.018147, 0.017922, 0.017702, 0.017487, 0.017278, 0.017074, 0.016874, 0.016679, 0.016488, 0.016302, 0.016120, 0.015942, 0.015767, 0.015597, 0.015430, 0.015267, 0.015107, 0.014950, 0.014797, 0.014647, 0.014500, 0.014355, 0.014214, 0.014075, 0.013939, 0.013806, 0.013675, 0.013547, 0.013421, 0.013297, 0.013175, 0.013056, 0.012939, 0.012824, 0.012711, 0.012600, 0.012491, 0.012384, 0.012278, 0.012175, 0.012073, 0.011973, 0.011874, 0.011777, 0.011682, 0.011588, 0.011496, 0.011405, 0.011315, 0.011227, 0.011141, 0.011055, 0.010971, 0.010888, 0.010807, 0.010726, 0.010647, 0.010569, 0.010492, 0.010417, 0.010342, 0.010268, 0.010196, 0.010124, 0.010054, 0.009984, 0.009915, 0.009848, 0.009781, 0.009715, 0.009650, 0.009586, 0.009523, 0.009460, 0.009399, 0.009338, 0.009278, 0.009219, 0.009160, 0.009102, 0.009045, 0.008989, 0.008933, 0.008878, 0.008824, 0.008770, 0.008717, 0.008665, 0.008613, 0.008562, 0.008511, 0.008462, 0.008412, 0.008363, 0.008315, 0.008268, 0.008221, 0.008174, 0.008128, 0.008082, 0.008037, 0.007993, 0.007949, 0.007905, 0.007862, 0.007820, 0.007777, 0.007736, 0.007694, 0.007654, 0.007613, 0.007573, 0.007534, 0.007495, 0.007456, 0.007417, 0.007380, 0.007342, 0.007305, 0.007268, 0.007232, 0.007196, 0.007160, 0.007124, 0.007089, 0.007055, 0.007020, 0.006986, 0.006953, 0.006919, 0.006886, 0.006854, 0.006821, 0.006789, 0.006757, 0.006726, 0.006695, 0.006664, 0.006633, 0.006603, 0.006573, 0.006543, 0.006513, 0.006484, 0.006455, 0.006426, 0.006398, 0.006370, 0.006342, 0.006314, 0.006286, 0.006259, 0.006232, 0.006205, 0.006179, 0.006152, 0.006126, 0.006100, 0.006075, 0.006049, 0.006024, 0.005999, 0.005974, 0.005949, 0.005925, 0.005901, 0.005877, 0.005853, 0.005829, 0.005806, 0.005782, 0.005759, 0.005736, 0.005714, 0.005691, 0.005669, 0.005647 }; #define WIPE (1) #define DARKEN (0) /* * Zero out a given square in an image. */ static void wipe_square(unsigned char *data, int width, int height, int bytes_per_line, int value) { int x, y; unsigned char *p; for (y = 0; y < height; y++) { p = data + 3 * y * bytes_per_line; for (x = 0; x < (3 * width); x++) { if (!value) { (*p) >>= 1; p++; } else { *(p++) = '\0'; } } } } /* * Analyze the picture with heuristics, and decide if it's * real, realistic, or unreal. */ static int analyze(unsigned char *data, int width, int height, int bytes_per_line) { int x, y; unsigned int i, j; unsigned int stats[256]; double normalized[256]; double variance; double diff; int num_pixels; unsigned char *p; /* * Initialize some variables. */ num_pixels = 3 * width * height; memset(stats, 0, sizeof(stats)); /* * If you're rooting through the *entire* picture, the following * code is simpler than going over (x,y) bytes. */ if (width == bytes_per_line) { p = data; for (i = 0; i < num_pixels; i++) { stats[(*p++)]++; } /* * Loop over a block of the image data, gathering the raw pixel * frequencies . */ } else { for (y = 0; y < height; y++) { p = data + 3 * y * bytes_per_line; for (x = 0; x < (3 * width); x++) { stats[(*p++)]++; } } } /* * Print out the raw pixel statistics, if requested. */ if (pixelfile) { FILE *fp = fopen(pixelfile, "w+"); if (!fp) { fprintf(stderr, "isreal: failed to create %s: %s\n", pixelfile, strerror(errno)); exit(1); } for (i = 1; i < 256; i++) { fprintf(fp, "%d %f\n", i, ((double) stats[i]) / ((double) num_pixels)); } fclose(fp); } /* * Strange histogram folding operation */ for (i = 1; i < 256; i++) { for (j = i >> 1; j != 0; j >>= 1) { stats[j] += stats[i]; } } /* * Normalize the array for the number of pixels */ for (i = 1; i < 256; i++) { normalized[i] = ((double) stats[i]) / ((double) num_pixels); } /* * Print out the folded pixel statistics, if requested. */ if (foldedfile) { FILE *fp = fopen(foldedfile, "w+"); if (!fp) { fprintf(stderr, "isreal: failed to create %s: %s\n", foldedfile, strerror(errno)); exit(1); } for (i = 1; i < 256; i++) { fprintf(fp, "%d %f\n", i, normalized[i]); } fclose(fp); } /* * Compare the normalized numbers to the stock folded numbers, * via a pseudo-variance technique. */ variance = 0.; for (i = 1; i < 128; i++) { diff = (normalized[i] - folded[i]); variance += diff * diff; } /* * If the variance is REALLY high, then it's obviously not real. * * The value is chosen only because it seems to work. */ if (variance > RAW_VARIANCE_UNREAL) { return UNREAL; } /* * If the variance is medium, then it's realistic. * * The value is chosen only because it seems to work. */ if (variance > RAW_VARIANCE_REALISTIC) { return REALISTIC; } /* * Maybe it's a second stage unreal? * * Handle images which on first glance look real, * but which don't have the fine-grained real structure to them. */ variance = 0.; for (i = 1; i < 128; i++) { /* * Ensure we don't take the log of zero. * * If the normalized pixel count contains NO pixels of that * color, then don't count it in the variance. */ if (normalized[i] != 0.) { diff = log(normalized[i]) - log(folded[i]); } else { diff = 0.; } variance += diff * diff; } /* * If the variance is low in normal scale, but high at log-scale, * then it's realistic, but definitely not real. * * Again, the value here has no meaning, other than it seems to work. */ if (variance > LOG_VARIANCE_REALISTIC_2) { return REALISTIC_2; } if (variance > LOG_VARIANCE_REALISTIC) { return REALISTIC; } /* * The variance is low at normal scale, AND at log scale. * Therefore, it must be a real picture. */ return REAL; } /* * Recursively go over the whole picture, classifying bits of it * as {real, realistic} versus unreal. Any unreal portions are * wiped to '0', which I hope is black... */ static void recurse(unsigned char *data, int startx, int starty, int width, int height, int bytes_per_line) { int x, y; int flag; unsigned char *p; p = data + 3 * (startx + starty * bytes_per_line); /* * If there are too few pixels in this image to recurse, then * zero out the area, if it's unreal. */ if ((width * height) < boundary) { /* * Classify the area as real or unreal; */ flag = analyze(p, width, height, bytes_per_line); if (flag == UNREAL) wipe_square(p, width, height, bytes_per_line, WIPE); if (flag != REAL) wipe_square(p, width, height, bytes_per_line, DARKEN); return; /* stop recursing */ } /* * Otherwise, we recurse over the image, in halves */ for (y = 0; y < (height - 3); y += (height / 2)) { int wy; wy = (height / 2); if (wy == y) wy = height - wy; for (x = 0; x < (width - 3); x += (width / 2)) { int wx; wx = (width / 2); if (wx == x) wx = width - wx; recurse(data, startx + x, starty + y, wx, wy, bytes_per_line); } } #if 0 if (!wipe) { if (flag == UNREAL) wipe_square(p, width, height, bytes_per_line); } #endif } static void usage(void) { printf("Usage: isreal \n\n"); printf(" This program classifies an image into one of three types:\n\n"); printf(" unreal Not a picture of a real-life thing.\n"); printf(" realistic A realistic, but computer generated or modified picture.\n"); printf(" real A real picture of a real-life thing.\n"); printf("\n"); printf(" Command line options:\n"); printf(" -p file outputs the raw relative pixel frequency to file.\n"); printf(" -f file outputs the folded relative pixel frequency to file.\n"); printf(" -r re-write image.ext to image-isreal.ext, with annotations\n"); printf(" -b size sets smallest block size for annotations to size*size (DEFAULT 32)\n"); printf("\n"); exit(1); } int main(int argc, char **argv) { Display *disp; ImlibData *id; ImlibImage *im; int w,h; int rcode; int i; int rewrite = FALSE; int argval; while ((argval = getopt(argc, argv, "b:f:p:hrw")) != EOF) { switch (argval) { case 'b': w = atoi(optarg); if (w < 10) { fprintf(stderr, "isreal: value for '-b' is too small to produce valid results: setting to 10.\n"); w = 10; } boundary = w * w; break; case 'f': foldedfile = optarg; break; case 'p': pixelfile = optarg; break; case 'r': rewrite = TRUE; break; case 'w': wipe = TRUE; break; default: case 'h': usage(); break; } } /* * Can't do two different things at the same time... */ if (rewrite && (foldedfile || pixelfile)) { fprintf(stderr, "isreal: -r is incompatible with -f or -p.\n"); exit(1); } /* * Check for compatible parameters */ if (wipe && !rewrite) { fprintf(stderr, "isreal: -w specified without -r... ignoring.\n"); } /* Be nice and tell the user if they don't, to provide a file as an arg */ if (argc <= optind) { usage(); } /* Connect to the default Xserver */ disp = XOpenDisplay(NULL); /* Immediately afterwards Intitialise Imlib */ id = Imlib_init(disp); for (i = optind; i < argc; i++) { /* * Skip previously analyzed images. */ if (strstr(argv[i], "-isreal") != NULL) { continue; } /* Load the image specified as the first argument */ im = Imlib_load_image(id, argv[i]); if (!im) { fprintf(stderr, "failed to load image %s\n", argv[i]); exit(1); } /* Suck the image's original width and height out of the Image structure */ w = im->rgb_width;h = im->rgb_height; /* analyze the data */ if (!rewrite) { printf("%-30s ", argv[i]); rcode = analyze(im->rgb_data, w, h, w); switch (rcode) { case REAL: printf("real\n"); break; case REALISTIC: printf("realistic\n"); break; /* * This one shows up mainly for ray-traced images. */ case REALISTIC_2: printf("realistic (probably ray-traced)\n"); break; default: case UNREAL: printf("unreal\n"); break; } /* * If we've been asked to re-write the image with pieces * classified as unreal/realistic, then re-name the file to * foo-isreal.bar, and output the hacked image. */ } else { char *p; char buffer[2048]; char suffix[16]; /* * Go analyze the image */ recurse(im->rgb_data, 0, 0, w, h, w); /* * Re-write the file name */ strcpy(buffer, argv[i]); p = buffer + strlen(buffer); while ((p > buffer) && (*p != '.')) { p--; } if (p == buffer) continue; strcpy(suffix, p); strcpy(p, "-isreal"); p += strlen(p); strcpy(p, suffix); /* * Save the modified file. */ Imlib_save_image(id, im, buffer, NULL); } /* * And destroy the image, so we don't eat too much RAM. */ Imlib_destroy_image(id, im); } /* end of loop over argv[] */ return 0; }