#!/usr/bin/nickle autoimport Cairo; autoload PRNG; int sign(int x) { return x == 0 ? 0 : x < 0 ? -1 : 1; } int X_AXIS = 0; int Y_AXIS = 1; typedef struct { int major; int minor; int sign_major; int sign_minor; int e; int e1; int e3; bool first; } clip_context; typedef struct { int maj1, min1, maj2, min2; } clip_box; typedef struct { int x1, y1, x2, y2; } box; typedef struct { int x, y; } point; typedef struct { int x1, y1, x2, y2; box b; point[] clipped; point[] run; } test; box bounds = { .x1 = 10, .x2 = 30, .y1 = 10, .y2 = 30 }; int div_ceil(a, b) { a += b; assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b); return (a + b - 1) // b - 1; } int div_floor_plus_one(a, b) { a += b; assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b); return a // b; } bool clip(*clip_context c, *clip_box b) { int adjust_major = 0, adjust_minor = 0; /* Clip major axis */ if (c->major < b->maj1) { if (c->sign_major <= 0) return false; adjust_major = b->maj1 - c->major; } else if (c->major >= b->maj2) { if (c->sign_major >= 0) return false; adjust_major = c->major - (b->maj2-1); } /* Clip minor axis */ if (c->minor < b->min1) { if (c->sign_minor <= 0) return false; adjust_minor = b->min1 - c->minor; } else if (c->minor >= b->min2) { if (c->sign_minor >= 0) return false; adjust_minor = c->minor - (b->min2-1); } /* If unclipped, we're done */ if (adjust_major == 0 && adjust_minor == 0) return true; /* See how much minor adjustment would happen during * a major clip. This is a bit tricky because line drawing * isn't symmetrical when the line passes exactly between * two pixels, we have to pick which one gets drawn */ int adj_min; if (!c->first) adj_min = div_ceil(c->e + adjust_major * c->e1, -c->e3); else adj_min = div_floor_plus_one(c->e + adjust_major * c->e1, -c->e3); /* Compare that to the minor clip and pick * the larger amount. */ printf ("\tinitial major %d minor %d error %d e1 %d e3 %d\n", c->major, c->minor, c->e, c->e1, c->e3); if (adj_min < adjust_minor) { printf("\tminor clip dominates %d < %d. adjust major %d -> ", adj_min, adjust_minor, adjust_major); if (c->first) adjust_major = div_ceil(c->e - adjust_minor * c->e3, c->e1); else adjust_major = div_floor_plus_one(c->e - adjust_minor * c->e3, c->e1); printf("%d\n", adjust_major); } else { printf("\tminor clip dominates %d > %d. adjust minor %d -> ", adj_min, adjust_minor, adjust_minor); adjust_minor = adj_min; printf("%d\n", adjust_minor); } c->e += adjust_major * c->e1 + adjust_minor * c->e3; c->major += c->sign_major * adjust_major; c->minor += c->sign_minor * adjust_minor; printf ("\tadjust major %d adjust minor %d e %d e1 %d e3 %e\n", adjust_major, adjust_minor, c->e, c->e1, c->e3); if (c->e >= 0) printf ("error positive e %d e1 %d e3 %d\n", c->e, c->e1, c->e3); if (c->e < c->e3) printf ("error magnitude too large e %d e1 %d e3 %d\n", c->e, c->e1, c->e3); return true; } test line(int x1, int y1, int x2, int y2, *box b) { int dx = x2 - x1; int dy = y2 - y1; int signdx = sign(dx); int signdy = sign(dy); int adx = abs(dx); int ady = abs(dy); int axis; int e, e1, e2, e3; int len; clip_context clip_1, clip_2; clip_box c; bool clipped = false; test t = { .x1 = x1, .y1 = y1, .x2 = x2, .y2 = y2, .b = *b, .clipped = (point[...]) {}, .run = (point[...]) {} }; if (adx >= ady) { axis = X_AXIS; e1 = ady << 1; e2 = e1 - (adx << 1); e = e1 - adx; len = adx; clip_1.major = x1; clip_1.minor = y1; clip_2.major = x2; clip_2.minor = y2; clip_1.sign_major = signdx; clip_1.sign_minor = signdy; c.maj1 = b->x1; c.maj2 = b->x2; c.min1 = b->y1; c.min2 = b->y2; } else { axis = Y_AXIS; e1 = adx << 1; e2 = e1 - (ady << 1); e = e1 - ady; len = ady; clip_1.major = y1; clip_1.minor = x1; clip_2.major = y2; clip_2.minor = x2; clip_1.sign_major = signdy; clip_1.sign_minor = signdx; c.maj1 = b->y1; c.maj2 = b->y2; c.min1 = b->x1; c.min2 = b->x2; } e3 = e2 - e1; e = e - e1; clip_1.first = true; clip_2.first = false; clip_2.e = clip_1.e = e; clip_2.e1 = clip_1.e1 = e1; clip_2.e3 = clip_1.e3 = e3; clip_2.sign_major = -clip_1.sign_major; clip_2.sign_minor = -clip_1.sign_minor; printf ("clip start:\n"); if (!clip(&clip_1, &c)) clipped = true; printf("clip end:\n"); if (!clip(&clip_2, &c)) clipped = true; int clip_len; int clip_x, clip_y; int clip_e; int x_major, x_minor; int y_major, y_minor; clip_len = clip_1.sign_major * (clip_2.major - clip_1.major); if (clip_len < 0) clipped = true; int x, y; if (axis == X_AXIS) { x = clip_1.major; y = clip_1.minor; x_major = clip_1.sign_major; x_minor = 0; y_major = 0; y_minor = clip_1.sign_minor; } else { x = clip_1.minor; y = clip_1.major; x_major = 0; x_minor = clip_1.sign_minor; y_major = clip_1.sign_major; y_minor = 0; } clip_e = clip_1.e; if (clipped) clip_len = -1; while (clip_len-- >= 0) { t.clipped[dim(t.clipped)] = (point) { .x = x, .y = y }; x += x_major; y += y_major; clip_e += e1; if (clip_e >= 0) { x += x_minor; y += y_minor; clip_e += e3; } } x = x1; y = y1; while (len-- >= 0) { if (bounds.x1 <= x && x < bounds.x2 && bounds.y1 <= y && y < bounds.y2) { t.run[dim(t.run)] = (point) { .x = x, .y = y }; } x += x_major; y += y_major; e += e1; if (e >= 0) { x += x_minor; y += y_minor; e += e3; } } return t; } void read_events (Cairo::cairo_t cr) { file event = Cairo::open_event(cr); while (!File::end(event)) { string event_line = File::fgets(event); if (String::index(event_line, "delete") >= 0) exit(0); } } #for (int y = 0; y < 20; y++) void show(cairo_t cr, test t) { rectangle(cr, 0, 0, 40, 40); set_source_rgba(cr, 1, 1, 1, 1); fill(cr); set_source_rgba(cr, 0, 1, 0, .2); set_line_width(cr, 0.1); for (int x = 0; x < 40; x++) { move_to(cr, 0, x); line_to(cr, 40, x); move_to(cr, x, 0); line_to(cr, x, 40); } stroke(cr); rectangle(cr, t.b.x1, t.b.y1, t.b.x2 - t.b.x1, t.b.y2 - t.b.y1); set_line_width(cr, 0.1); set_source_rgba(cr, 0, 0, 0, 1); stroke(cr); move_to(cr, t.x1+.5, t.y1+.5); line_to(cr, t.x2+.5, t.y2+.5); move_to(cr, t.x2, t.y2); line_to(cr, t.x2+1, t.y2+1); move_to(cr, t.x2+1, t.y2); line_to(cr, t.x2, t.y2+1); stroke(cr); void pixels(point[] pt) { for (int i = 0; i < dim(pt); i++) { rectangle(cr, pt[i].x, pt[i].y, 1, 1); } fill(cr); } set_source_rgba(cr, 1, 0, 0, .5); pixels(t.clipped); set_source_rgba(cr, 0, 0, 1, .5); pixels(t.run); } bool compare(test t) { if (dim(t.clipped) != dim(t.run)) return false; for (int i = 0; i < dim(t.clipped); i++) if (t.clipped[i] != t.run[i]) return false; return true; } void doit(int i) { int n; *box b = &bounds; cairo_t cr = new(800, 800); scale(cr, 20, 20); for (;;) { PRNG::srandom(i); int x1 = PRNG::randint(40); int x2 = PRNG::randint(40); int y1 = PRNG::randint(40); int y2 = PRNG::randint(40); test t = line (x1, y1, x2, y2, &bounds); show(cr, t); if (!compare(t)) { printf("line %d -- %d x %d - %d x %d\n", i, x1, y1, x2, y2); gets(); } i++; } read_events(cr); } int i = 0; if (dim(argv) > 1) i = atoi(argv[1]); doit(i);