/* ichika2.c - Don Yang (uguu.org) 11/20/04 */ #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 typedef float flt; static unsigned char *image; static int width, height, size, align, headersize; static char header[64]; 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( header[0] == 'B' ) ReorderBytes(); ProcessImage(); if( header[0] == 'B' ) ReorderBytes(); SaveImage(argv[2]); free(image); return 0; } static void rgb2hsv(void) { 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 { 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) { 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 = width)) == 0 ) return 1; if( CheckNeighbors0(offset + (k = -width)) == 0 ) return 1; return 0; } static int CheckNeighbors0(int offset) { if( offset < width || offset > size - width || (offset % width) == 0 || ((offset + 1) % width) == 0 ) return 0; r = (flt)(image[offset * 3]) / 255.0f; g = (flt)(image[offset * 3 + 1]) / 255.0f; b = (flt)(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] == 0xff ) return; mask[offset] = 0xff; x = offset % width; y = offset / width; if( x < width - 1 ) Erase(offset + 1); if( y < height - 1 ) Erase(offset + width); if( x > 0 ) Erase(offset - 1); if( y > 0 ) Erase(offset - width); } static int LoadImage(char *name) { if( (file = fopen(name, "rb")) == NULL ) { printf("can not open %s\n", name); return 1; } do { image = NULL; /* Parse header */ if( fread(header, 64, 1, file) != 1 ) { puts("read error"); break; } if( header[0] == 'B' && header[1] == 'M' ) { if( *((int*)&(header[28])) != 24 ) { puts("invalid BMP"); break; } width = *((int*)&(header[18])); height = *((int*)&(header[22])); align = (4 - ((width * 3) & 3)) & 3; headersize = 54; } else if( header[0] == 'P' && header[1] == '6' ) { for(i = j = headersize = 0; i < 63; i++) { if( header[i] == '\n' ) { if( ++j == 3 ) { header[headersize = ++i] = '\0'; break; } } } if( headersize == 0 ) { puts("invalid PPM"); break; } if( sscanf(header, "P6\n%d %d\n", &(width), &(height)) != 2 ) { puts("invalid PPM"); break; } align = 0; } else { puts("unrecognized input format"); break; } if( width < 16 || height < 16 ) { puts("invalid image size"); break; } /* Load image Note that BMPs will be loaded upside down, but we don't care. */ if( (image = (unsigned char*)malloc( (size = (width * height)) * 3)) == NULL ) { puts("out of memory"); break; } u = image; fseek(file, headersize, SEEK_SET); for(i = 0; i < height; i++) { if( fread(u, width * 3, 1, file) < 1 ) { puts("read error"); break; } for(j = 0; j < align; j++) fgetc(file); u += width * 3; } if( i < height ) break; fclose(file); return 0; } while(0); fclose(file); if( image != NULL ) free(image); return 1; } static void ProcessImage(void) { /* Create mask */ if( (mask = (unsigned char*)malloc(size)) == NULL ) return; u = image; m = mask; for(i = 0; i < size; i++) { r = (flt)*(u++) / 255.0f; g = (flt)*(u++) / 255.0f; b = (flt)*(u++) / 255.0f; rgb2hsv(); *m = 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 < width; i++) { Erase(i); Erase(i + width); Erase(i + size - width * 2); Erase(i + size - width); } for(i = 0; i < height; i++) { Erase(i * width); Erase(i * width + 1); Erase((i + 1) * width - 2); Erase((i + 1) * width - 1); } /* Remove areas not surrounded by skin/hair */ m = mask; for(i = 0; i < size; i++) { if( *m != 0xff ) { if( CheckNeighbors(i) != 0 ) Erase(i); } m++; } /* Shift hue */ u = image; m = mask; for(i = 0; i < size; i++) { if( *m != 0xff ) { 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 = image - 1; for(i = 0; i < 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; } fwrite(header, headersize, 1, file); u = image; for(i = 0; i < height; i++) { fwrite(u, width * 3, 1, file); for(j = 0; j < align; j++) fputc(0, file); u += width * 3; } fclose(file); }