#include #include #include #include #include #include #include #define PI 3.14159265358979323846264338327950288419716939937510 #define SQUARE_CORNER 0.25 /* Transformations. */ static double g[6], h[3]; /* Distortion parameters. */ static int phase[2][2], period[2][2]; /* Projected grid coordinates. */ static double grid_x, grid_y; /* Computed dot sizes. */ static double dot_size, min_dot, max_dot; /* Apply transformation from screen coordinates to grid coordinates, storing results in (grid_x, grid_y). */ static void ApplyGridTransform(double x, double y) { grid_x = g[0] * x + g[1] * y + g[2]; grid_y = g[3] * x + g[4] * y + g[5]; } /* Compute halftone dot size from grid coordinates, storing result in dot_size. */ static void ComputeDotSize(double x, double y) { dot_size = h[0] * x + h[1] * y + h[2]; } /* Update dot size ranges. */ static void UpdateDotSizeRange() { if( min_dot > dot_size ) min_dot = dot_size; if( max_dot < dot_size ) max_dot = dot_size; } /* Check if a grid point is within radius of a single nearby halftone dot, returns 1 if so. */ static int WithinDotRadius(double px, double py, double target_x, double target_y) { double dx = px - target_x; double dy = py - target_y; ComputeDotSize(target_x, target_y); return dx * dx + dy * dy < dot_size * dot_size; } /* Determine if a grid point is within the radius of nearby halftone dots, returns 1 if so. */ static int WithinRadiusOfNearbyDots() { return WithinDotRadius(grid_x, grid_y, floor(grid_x), floor(grid_y)) || WithinDotRadius(grid_x, grid_y, floor(grid_x), ceil(grid_y)) || WithinDotRadius(grid_x, grid_y, ceil(grid_x), floor(grid_y)) || WithinDotRadius(grid_x, grid_y, ceil(grid_x), ceil(grid_y)); } int main(int argc, char **argv) { png_image image; png_bytep buffer1, buffer2; double scale, angle, fx, fy, rx, ry; int s, i, j, k, x, y, d; if( argc != 4 ) return printf("%s \n", *argv); memset(&image, 0, sizeof(image)); image.version = PNG_IMAGE_VERSION; if( !png_image_begin_read_from_file(&image, argv[1]) ) return printf("Error reading %s\n", argv[1]); image.format = PNG_FORMAT_RGBA; s = image.width * image.height * 4; buffer1 = (png_bytep)malloc(s); buffer2 = (png_bytep)malloc(s); if( buffer1 == NULL || buffer2 == NULL ) return puts("Out of memory"); if( !png_image_finish_read(&image, NULL, buffer1, 0, NULL) ) return printf("Error loading %s\n", argv[1]); memcpy(buffer2, buffer1, s); srand(time(NULL)); /* Generate transformation from screen coordinates to grid coordinates. Halftone dots will be centered around integer intervals in grid coordinates, maximum distance between the center of any two dots will be sqrt(2). grid_x = g[0] * x + g[1] * y + g[2] grid_y = g[3] * x + g[4] * y + g[5] */ scale = (double)rand() / RAND_MAX * 90 + 30; scale /= image.width > image.height ? image.width : image.height; angle = ((double)rand() / RAND_MAX) * PI; g[0] = g[4] = cos(angle) * scale; g[1] = sin(angle) * scale; g[3] = -g[1]; g[2] = (double)rand() / RAND_MAX * 2.0; g[5] = (double)rand() / RAND_MAX * 2.0; /* Generate a second transformation from grid coordinates to gradient coordinates. The X value here will be used to compute halftone dot sizes. dot_size = h[0] * grid_x + h[1] * grid_y + h[2] Only the rotation matter for this transformation, we will compute scale and offset such that the dot radii are zero in one corner and 0.5*sqrt(2) in the other corner. */ angle = ((double)rand() / RAND_MAX) * 2.0 * PI; h[0] = cos(angle); h[1] = sin(angle); h[2] = 0; /* Apply rotation to the 4 corners of the rectangle enclosed by (0,0) .. (width,height) Note that it goes up to (width,height) as opposed to (width-1,height-1), thus the rectangle is always at least one pixel wide. */ ApplyGridTransform(0, 0); ComputeDotSize(grid_x, grid_y); min_dot = max_dot = dot_size; ApplyGridTransform(0, image.height); ComputeDotSize(grid_x, grid_y); UpdateDotSizeRange(); ApplyGridTransform(image.width, 0); ComputeDotSize(grid_x, grid_y); UpdateDotSizeRange(); ApplyGridTransform(image.width, image.height); ComputeDotSize(grid_x, grid_y); UpdateDotSizeRange(); assert(min_dot < max_dot); /* Update gradient transform so that corners of the screen cover the full size range of [0 .. 0.5*sqrt(2)]. */ scale = 0.5 * sqrt(2) / (max_dot - min_dot); h[0] *= scale; h[1] *= scale; h[2] = -min_dot * scale; /* Generate distortion parameters. */ for(x = 0; x < 2; x++) { for(y = 0; y < 2; y++) { phase[x][y] = (int)((double)rand() / RAND_MAX * 8.0); period[x][y] = (int)((double)rand() / RAND_MAX * 6.0) + 2; } } /* Render pixels. */ for(y = i = 0; y < (int)image.height; y++) { for(x = 0; x < (int)image.width; x++, i++) { ApplyGridTransform(x, y); if( sizeof('c') > 1 ) { /* C mode: halftone dots. */ s = WithinRadiusOfNearbyDots(); } else { /* C++ mode: distortion squares. */ /* Check if pixel is near corner of the grid cells. If not, set parity based on square coordinates. */ fx = floor(grid_x); fy = floor(grid_y); rx = grid_x - fx; ry = grid_y - fy; if( rx + ry < SQUARE_CORNER || rx + ry > 2.0 - SQUARE_CORNER || ry - rx > 1.0 - SQUARE_CORNER || rx - ry > 1.0 - SQUARE_CORNER ) { /* Near corner, determine which diagonal line the pixels are on. */ rx = round(grid_x); ry = round(grid_y); d = (int)(rx + ry) % 2 != 0; /* Apply wave function specific to that diagonal line, such that parity alternates every few corners along that line. */ for(j = s = 0; j < 2; j++) { k = (int)fmod(phase[j][d] + rx - j * ry, period[j][d]); if( k < 0 ) k += period[j][d]; s ^= k > period[j][d] / 2 ? 1 : 0; } /* Render corner as a 2x2 checkerboard. */ s ^= (grid_x - rx > fabs(grid_y - ry) || grid_x - rx < -fabs(grid_y - ry)) ? 1 : 0; } else { /* Away from corners. */ s = (int)(fx + fy) % 2; } } if( s ) memset(buffer1 + (i * 4), 0, 4); else memset(buffer2 + (i * 4), 0, 4); } } if( !png_image_write_to_file(&image, argv[2], 0, buffer1, 0, NULL) ) return printf("Error writing %s\n", argv[2]); if( !png_image_write_to_file(&image, argv[3], 0, buffer2, 0, NULL) ) return printf("Error writing %s\n", argv[3]); return 0; }