/* chinatsu.c - Don Yang (uguu.org) 01/02/12 */ #include #include #include typedef struct { int min_p, max_p; /* Primary pixel ranges */ int min_q, max_q; /* Secondary pixel ranges */ } Ranges; FILE *primary = NULL, *secondary = NULL, *output = NULL; int width, height, offset1, offset2, width2, height2; int i, c; int x, y, x0, y0; int step, ip0, ip1, iq0, iq1; unsigned char *buffer1 = NULL, *buffer2 = NULL; unsigned char pixel[12]; char header[256]; char pixels[256][256]; /* [primary][secondary] */ Ranges best, z, scale; int best_area, z_area; float p, q, r, s; /* Pixel schedules */ int index[2][12] = { /* Rc Gp Bc Rc Gc Bp Rp Gc Bc Rq Gq Bq */ {6, 9, 0, 3, 1, 10, 4, 7, 5, 11, 2, 8}, /* Rq Gq Bq Rp Gc Bc Rc Gc Bp Rc Gp Bc */ {3, 0, 6, 9, 10, 1, 4, 7, 8, 2, 5, 11} }; /* Parse PPM header, returning offset to pixel data */ int ParseHeader(FILE *infile) { fseek(infile, 0, SEEK_SET); if( (int)fread(header, 1, sizeof(header), infile) <= 0 || sscanf(header, "P6 %d %d %d", &width, &height, &i) != 3 || i != 255 ) return 0; for(i = c = 0; i < sizeof(header); i++) if( header[i] == '\n' ) if( ++c == 3 ) return i + 1; return 0; } /* Scale an input value from [0,256) to new range */ int ScaleValue(int input, int range_min, int range_max) { return input * (range_max - range_min) / 256 + range_min; } float ScaleValueF(int input, int range_min, int range_max) { return (float)input * (range_max - range_min) / 256.f + range_min; } /* Compute area of the selected range */ int ComputeArea(void) { return (z.max_p - z.min_p) * (z.max_q - z.min_q); } /* Convert floating point pixel value to integer */ unsigned char ConvertToInt(float p) { ip0 = (int)p; s = p - (float)ip0; /* Use random number generator for dithering. This random number generator is not seeded, so the output may be deterministic. We don't really care either way for our purpose. */ if( ((float)rand() / (float)RAND_MAX) < s ) ip0++; return ip0 < 0 ? 0 : (ip0 > 255 ? 255 : ip0); } /* Convert input pixel combinations to output pixel values */ void MergePixels(unsigned char p_in, unsigned char q_in, unsigned char *p_out, unsigned char *q_out, unsigned char *c1_out, unsigned char *c2_out) { p = ScaleValueF(p_in, scale.min_p, scale.max_p); q = ScaleValueF(q_in, scale.min_q, scale.max_q); r = (3 * p - q) / 2.0f; *p_out = ConvertToInt(p); *q_out = ConvertToInt(q); *c1_out = *c2_out = ConvertToInt(r); } int main(int argc, char **argv) { /* Check command line arguments */ if( argc < 4 ) { printf("%s [odd]\n", *argv); } else { /* Check primary image */ if( (primary = fopen(argv[1], "rb")) == NULL ) { printf("Error opening %s for reading\n", argv[1]); } else { offset1 = ParseHeader(primary); width2 = width; height2 = height; if( offset1 == 0 ) { printf("Error reading header from %s\n", argv[1]); } else { /* Check secondary image */ if( (secondary = fopen(argv[2], "rb")) == NULL ) { printf("Error opening %s for reading\n", argv[2]); } else { offset2 = ParseHeader(secondary); if( offset2 == 0 ) { printf("Error reading header from %s\n", argv[2]); } else { if( width != width2 || height != height2 ) { puts("Image dimensions mismatched"); } else { /* Allocate scanline buffers. These usually succeeds unless image dimensions were bad. */ buffer1 = (unsigned char*)malloc(width * 3); buffer2 = (unsigned char*)malloc(width * 3); if( buffer1 == NULL || buffer2 == NULL ) { printf("Not enough memory for %d pixels\n", width); } else { /* Compute scaling parameters */ fseek(primary, offset1, SEEK_SET); fseek(secondary, offset2, SEEK_SET); /* Mark all pixel combinations as unused */ memset(pixels, 0, sizeof(pixels)); /* Record all pixel combinations */ for(y = 0; y < height; y++) { fread(buffer1, width * 3, 1, primary); fread(buffer2, width * 3, 1, secondary); for(x = 0; x < width * 3; x++) pixels[buffer1[x]][buffer2[x]] = 1; } /* Set initial range and try to expand outward */ scale.min_p = scale.min_q = 64; scale.max_p = scale.max_q = 256 - 64; memcpy(&z, &scale, sizeof(z)); memcpy(&best, &scale, sizeof(best)); best_area = ComputeArea(); for(step = 64; step > 0; step /= 2) { for(ip0 = -1; ip0 <= 0; ip0++) { z.min_p = scale.min_p + ip0 * step; if( z.min_p >= 0 ) { for(iq0 = -1; iq0 <= 0; iq0++) { z.min_q = scale.min_q + iq0 * step; if( z.min_q >= 0 ) { for(ip1 = 1; ip1 >= 0; ip1--) { z.max_p = scale.max_p + ip1 * step; if( z.max_p <= 256 ) { for(iq1 = 1; iq1 >= 0; iq1--) { z.max_q = scale.max_q + iq1 * step; if( z.max_q <= 256 ) { z_area = ComputeArea(); if( z_area > best_area ) { for(x = c = 0; x < 256 && c >= 0 && c < 256; x++) { x0 = ScaleValue(x, z.min_p, z.max_p); for(y = 0; y < 256 && c >= 0 && c < 256; y++) { y0 = ScaleValue(y, z.min_q, z.max_q); if( pixels[x][y] != 0 ) c = (3 * x0 - y0) / 2; } } if( c >= 0 && c < 256 ) { memcpy(&best, &z, sizeof(z)); best_area = z_area; } } } } } } } } } } memcpy(&scale, &best, sizeof(best)); } /* Open output */ if( (output = fopen(argv[3], "wb+")) == NULL ) { printf("Can not open %s for writing\n", argv[3]); } else { /* Write output header */ fprintf(output, "P6\n%d %d\n255\n", width * 2, height * 2); /* Merge images */ fseek(primary, offset1, SEEK_SET); fseek(secondary, offset2, SEEK_SET); c = argc > 4 ? 1 : 0; for(y = 0; y < height; y++) { fread(buffer1, width * 3, 1, primary); fread(buffer2, width * 3, 1, secondary); for(step = 0; step <= 6; step += 6) { for(x = 0; x < width; x++) { /* R */ MergePixels(buffer1[x * 3], buffer2[x * 3], &pixel[index[c][0]], &pixel[index[c][1]], &pixel[index[c][2]], &pixel[index[c][3]]); /* G */ MergePixels(buffer1[x * 3 + 1], buffer2[x * 3 + 1], &pixel[index[c][4]], &pixel[index[c][5]], &pixel[index[c][6]], &pixel[index[c][7]]); /* B */ MergePixels(buffer1[x * 3 + 2], buffer2[x * 3 + 2], &pixel[index[c][8]], &pixel[index[c][9]], &pixel[index[c][10]], &pixel[index[c][11]]); fwrite(pixel + step, 6, 1, output); } } } } } } } } } } } if( buffer1 != NULL ) free(buffer1); if( buffer2 != NULL ) free(buffer2); if( primary != NULL ) fclose(primary); if( secondary != NULL ) fclose(secondary); if( output != NULL ) return fclose(output); return 1; }