/* ichika1.c - Don Yang (uguu.org) 11/20/04 */ /*@ -globstate -mustfreeonly -onlytrans -realcompare -statictrans @*/ #include #include #include #define SHIFT_HUE 2.0f #define SHIFT_SAT 0.70f #define SHIFT_VAL 0.09f #define EYE_HUE_MIN 3.60f #define EYE_HUE_MAX 4.24f #define EYE_SAT1 0.65f #define EYE_VAL1 0.42f #define EYE_SAT2 0.36f #define EYE_VAL2 0.60f #define EYE_THRESHOLD 0.6f #define EYE_SCALE_VAL 1.29f #define FACE_HUE1_MIN 0.30f #define FACE_HUE1_MAX 0.45f #define FACE_HUE2_MIN 4.28f #define FACE_HUE2_MAX 6.00f #define HAIR_HUE_MIN 0.0f #define HAIR_HUE_MAX 0.1f #if 0 typedef double flt; #else typedef float flt; #endif static struct { /*@only@*/unsigned char *image; int width, height, size, align, headersize; char header[64]; } img; static flt r, g, b, h, s, v; static flt r0, g0, b0, d1, d2; static flt f, p, q, t; static unsigned char *mask, *m, *u, c; static int i, j, k; static FILE *file; static void rgb2hsv(void); static void hsv2rgb(void); static int CheckNeighbors(int offset); static int CheckNeighbors0(int offset); static void Erase(int offset); static int LoadImage(char *name); static void ProcessImage(void); static void ReorderBytes(void); static void SaveImage(char *name); int main(int argc, char **argv) { if( argc < 3 ) return printf("%s \n", *argv); if( LoadImage(argv[1]) != 0 ) return -1; if( img.header[0] == 'B' ) ReorderBytes(); ProcessImage(); if( img.header[0] == 'B' ) ReorderBytes(); SaveImage(argv[2]); free(img.image); return 0; } static void rgb2hsv(void) { assert(r >= 0.0f && r <= 1.0f); assert(g >= 0.0f && g <= 1.0f); assert(b >= 0.0f && b <= 1.0f); p = q = r; if( p > g ) p = g; if( q < g ) q = g; if( p > b ) p = b; if( q < b ) q = b; if( p == q ) { h = s = v = 0.0f; } else { assert(q > 0.0f); p = q - p; if( r == q ) { h = (g - b) / p; } else if( g == q ) { h = (b - r) / p + 2.0f; } else /* b == q */ { h = (r - g) / p + 4.0f; } s = p / (v = q); } } static void hsv2rgb(void) { assert(h >= 0.0f && h < 6.0f); assert(s >= 0.0f && s <= 1.0f); assert(v >= 0.0f && v <= 1.0f); k = (int)h; f = h - (flt)k; p = v * (1.0f - s); q = v * (1.0f - s * f); t = v * (1.0f - s * (1.0f - f)); switch( k ) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; default: r = v; g = p; b = q; break; } } static int CheckNeighbors(int offset) { if( CheckNeighbors0(offset + (k = 1)) == 0 ) return 1; if( CheckNeighbors0(offset + (k = -1)) == 0 ) return 1; if( CheckNeighbors0(offset + (k = img.width)) == 0 ) return 1; if( CheckNeighbors0(offset + (k = -img.width)) == 0 ) return 1; return 0; } static int CheckNeighbors0(int offset) { if( offset < img.width || offset > img.size - img.width || (offset % img.width) == 0 || ((offset + 1) % img.width) == 0 ) return 0; r = (flt)(img.image[offset * 3]) / 255.0f; g = (flt)(img.image[offset * 3 + 1]) / 255.0f; b = (flt)(img.image[offset * 3 + 2]) / 255.0f; rgb2hsv(); if( (FACE_HUE1_MIN < h && h < FACE_HUE1_MAX) || (FACE_HUE2_MIN < h && h < FACE_HUE2_MAX) || (HAIR_HUE_MIN < h && h < HAIR_HUE_MAX) ) return 1; return CheckNeighbors0(offset + k); } static void Erase(int offset) { int x, y; if( mask[offset] == (unsigned char)0xff ) return; mask[offset] = (unsigned char)0xff; x = offset % img.width; y = offset / img.width; if( x < img.width - 1 ) Erase(offset + 1); if( y < img.height - 1 ) Erase(offset + img.width); if( x > 0 ) Erase(offset - 1); if( y > 0 ) Erase(offset - img.width); } static int LoadImage(char *name) { if( (file = fopen(name, "rb")) == NULL ) { printf("can not open %s\n", name); return 1; } do { img.image = NULL; /* Parse header */ if( fread(img.header, 64, 1, file) != 1 ) { (void)puts("read error"); break; } if( img.header[0] == 'B' && img.header[1] == 'M' ) { if( *((int*)&(img.header[28])) != 24 ) { (void)puts("invalid BMP"); break; } img.width = *((int*)&(img.header[18])); img.height = *((int*)&(img.header[22])); img.align = (4 - ((img.width * 3) & 3)) & 3; img.headersize = 54; } else if( img.header[0] == 'P' && img.header[1] == '6' ) { for(i = j = img.headersize = 0; i < 63; i++) { if( img.header[i] == '\n' ) { if( ++j == 3 ) { img.header[img.headersize = ++i] = '\0'; break; } } } if( img.headersize == 0 ) { (void)puts("invalid PPM"); break; } if( sscanf(img.header, "P6\n%d %d\n", &(img.width), &(img.height)) != 2 ) { (void)puts("invalid PPM"); break; } img.align = 0; } else { (void)puts("unrecognized input format"); break; } if( img.width < 16 || img.height < 16 ) { (void)puts("invalid image size"); break; } /* Load image Note that BMPs will be loaded upside down, but we don't care. */ if( (img.image = (unsigned char*)malloc( (size_t)(img.size = (img.width * img.height)) * 3)) == NULL ) { (void)puts("out of memory"); break; } u = img.image; (void)fseek(file, (long)img.headersize, SEEK_SET); for(i = 0; i < img.height; i++) { if( fread(u, (size_t)img.width * 3, 1, file) < 1 ) { (void)puts("read error"); break; } for(j = 0; j < img.align; j++) (void)fgetc(file); u += img.width * 3; } if( i < img.height ) break; (void)fclose(file); return 0; } while(0); (void)fclose(file); if( img.image != NULL ) free(img.image); return 1; } static void ProcessImage(void) { /* Create mask */ if( (mask = (unsigned char*)malloc((size_t)img.size)) == NULL ) return; u = img.image; m = mask; for(i = 0; i < img.size; i++) { r = (flt)*(u++) / 255.0f; g = (flt)*(u++) / 255.0f; b = (flt)*(u++) / 255.0f; rgb2hsv(); *m = (unsigned char)0xff; if( EYE_HUE_MIN < h && h < EYE_HUE_MAX ) { d1 = (s-EYE_SAT1)*(s-EYE_SAT1) + (v-EYE_VAL1)*(v-EYE_VAL1) * EYE_SCALE_VAL; d2 = (s-EYE_SAT2)*(s-EYE_SAT2) + (v-EYE_VAL2)*(v-EYE_VAL2) * EYE_SCALE_VAL; if( d1 < EYE_THRESHOLD || d2 < EYE_THRESHOLD ) { d1 = (d1 < EYE_THRESHOLD) ? (d1 / EYE_THRESHOLD) : (d2 / EYE_THRESHOLD); *m = (unsigned char)(255.0f * d1); } } m++; } /* Remove mask areas at edges */ for(i = 0; i < img.width; i++) { Erase(i); Erase(i + img.width); Erase(i + img.size - img.width * 2); Erase(i + img.size - img.width); } for(i = 0; i < img.height; i++) { Erase(i * img.width); Erase(i * img.width + 1); Erase((i + 1) * img.width - 2); Erase((i + 1) * img.width - 1); } /* Remove areas not surrounded by skin/hair */ m = mask; for(i = 0; i < img.size; i++) { if( *m != (unsigned char)0xff ) { if( CheckNeighbors(i) != 0 ) Erase(i); } m++; } /* Shift hue */ u = img.image; m = mask; for(i = 0; i < img.size; i++) { if( *m != (unsigned char)0xff ) { assert(i > img.width * 2 && i < img.size - img.width * 2); d1 = (flt)*m / 255.0f; d2 = 1.0f - d1; r = r0 = (flt)(u[0]) / 255.0f; g = g0 = (flt)(u[1]) / 255.0f; b = b0 = (flt)(u[2]) / 255.0f; rgb2hsv(); if( (h += SHIFT_HUE) >= 6.0f ) h -= 6.0f; if( (s += SHIFT_SAT) > 1.0f ) s = 1.0f; if( (v += SHIFT_VAL) > 1.0f ) v = 1.0f; hsv2rgb(); r = r0 * d1 + r * d2; g = r0 * d1 + g * d2; b = r0 * d1 + b * d2; u[0] = (unsigned char)(r * 255.0f); u[1] = (unsigned char)(g * 255.0f); u[2] = (unsigned char)(b * 255.0f); } u += 3; m++; } free(mask); } static void ReorderBytes(void) { /* Convert BMP's BGR to RGB */ u = img.image - 1; for(i = 0; i < img.size; i++) { c = *++u; *u = *(u + 2); *(u += 2) = c; } } static void SaveImage(char *name) { if( (file = fopen(name, "wb+")) == NULL ) { printf("can not create %s\n", name); return; } (void)fwrite(img.header, (size_t)img.headersize, 1, file); u = img.image; for(i = 0; i < img.height; i++) { (void)fwrite(u, (size_t)img.width * 3, 1, file); for(j = 0; j < img.align; j++) (void)fputc(0, file); u += img.width * 3; } (void)fclose(file); }