keystone.5c revision 4a908991
1/* 2 * Copyright © 2008 Keith Packard 3 * 4 * Permission to use, copy, modify, distribute, and sell this software and its 5 * documentation for any purpose is hereby granted without fee, provided that 6 * the above copyright notice appear in all copies and that both that copyright 7 * notice and this permission notice appear in supporting documentation, and 8 * that the name of the copyright holders not be used in advertising or 9 * publicity pertaining to distribution of the software without specific, 10 * written prior permission. The copyright holders make no representations 11 * about the suitability of this software for any purpose. It is provided "as 12 * is" without express or implied warranty. 13 * 14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO 16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR 17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 20 * OF THIS SOFTWARE. 21 */ 22 23autoload Process; 24autoload Nichrome; 25autoload Nichrome::Box; 26autoload Nichrome::Label; 27autoload Nichrome::Button; 28 29extend namespace Nichrome { 30 public namespace Quad { 31 public typedef quad_t; 32 public typedef widget_t + struct { 33 point_t[4] p; 34 real line_width; 35 real corner_diameter; 36 rgba_color_t line_color; 37 rgba_color_t corner_color; 38 bool down; 39 bool started; 40 int active_corner; 41 void(&quad_t) callback; 42 } quad_t; 43 44 protected void outline (cairo_t cr, &quad_t quad) { 45 for (int i = 0; i < dim (quad.p); i++) { 46 arc (cr, quad.p[i].x, quad.p[i].y, 47 quad.corner_diameter / 2, 0, 2 * pi); 48 close_path (cr); 49 } 50 } 51 52 protected void natural (cairo_t cr, &quad_t quad) { 53 rectangle (cr, 0, 0, 400, 300); 54 } 55 56 void text_at (cairo_t cr, point_t p, string text) { 57 text_extents_t e = text_extents (cr, text); 58 p.x = p.x - e.width / 2 - e.x_bearing; 59 p.y = p.y - e.height / 2 - e.y_bearing; 60 move_to (cr, p.x, p.y); 61 show_text (cr, text); 62 } 63 64 protected void draw (cairo_t cr, &quad_t quad) { 65 if (!quad.started) { 66 quad.p[2].x = quad.p[1].x = quad.geometry.width; 67 quad.p[3].y = quad.p[2].y = quad.geometry.height; 68 quad.started = true; 69 } 70 rectangle (cr, 0, 0, quad.geometry.width, quad.geometry.height); 71 set_source_rgba (cr, 0, 0, 0, .25); 72 fill (cr); 73 for (int i = 0; i < dim (quad.p); i++) 74 line_to (cr, quad.p[i].x, quad.p[i].y); 75 close_path (cr); 76 set_line_width (cr, quad.line_width); 77 set_source_rgba (cr, quad.line_color.red, quad.line_color.green, 78 quad.line_color.blue, quad.line_color.alpha); 79 set_line_join (cr, line_join_t.ROUND); 80 stroke (cr); 81 set_source_rgba (cr, quad.corner_color.red, quad.corner_color.green, 82 quad.corner_color.blue, quad.corner_color.alpha); 83 outline (cr, &quad); 84 fill (cr); 85 set_source_rgba (cr, 1, 1, 1, 1); 86 for (int i = 0; i < dim (quad.p); i++) 87 text_at (cr, quad.p[i], sprintf ("%d", i)); 88 } 89 90 int nearest (&quad_t quad, point_t p) { 91 real best_dist2 = 0; 92 int best = 0; 93 94 for (int i = 0; i < dim (quad.p); i++) { 95 real dist2 = ((p.x - quad.p[i].x) ** 2 + 96 (p.y - quad.p[i].y) ** 2); 97 if (i == 0 || dist2 < best_dist2) { 98 best_dist2 = dist2; 99 best = i; 100 } 101 } 102 return best; 103 } 104 105 protected void button (&quad_t quad, &button_event_t event) { 106 enum switch (event.type) { 107 case press: 108 quad.down = true; 109 quad.active_corner = nearest (&quad, event); 110 break; 111 case release: 112 quad.down = false; 113 break; 114 default: 115 break; 116 } 117 } 118 119 protected void motion (&quad_t quad, &motion_event_t motion) { 120 if (quad.down) { 121 motion.x = max (0, min (quad.geometry.width, motion.x)); 122 motion.y = max (0, min (quad.geometry.height, motion.y)); 123 quad.p[quad.active_corner].x = motion.x; 124 quad.p[quad.active_corner].y = motion.y; 125 quad.callback (&quad); 126 Widget::reoutline (&quad); 127 Widget::redraw (&quad); 128 } 129 } 130 131 protected void configure (&quad_t quad, 132 rect_t geometry) 133 { 134 if (quad.geometry.width > 0 && quad.geometry.height > 0) 135 { 136 real x_scale = geometry.width / quad.geometry.width; 137 real y_scale = geometry.height / quad.geometry.height; 138 for (int i = 0; i< 4; i++) { 139 quad.p[i].x *= x_scale; 140 quad.p[i].y *= y_scale; 141 } 142 } 143 Widget::configure (&quad, geometry); 144 quad.callback (&quad); 145 } 146 147 protected void init (&quad_t quad, 148 &nichrome_t nichrome, 149 void (&quad_t) callback) { 150 Widget::init (&nichrome, &quad); 151 quad.outline = outline; 152 quad.draw = draw; 153 quad.button = button; 154 quad.motion = motion; 155 quad.configure = configure; 156 quad.natural = natural; 157 quad.p = (point_t[4]) { 158 { x = 0, y = 0 } ... 159 }; 160 quad.line_color = (rgba_color_t) { 161 red = 1, green = 0, blue = 0, alpha = .5 162 }; 163 quad.line_width = 10; 164 quad.corner_color = (rgba_color_t) { 165 red = 0, green = 0, blue = 1, alpha = 0.75 166 }; 167 quad.corner_diameter = 20; 168 quad.down = false; 169 quad.active_corner = -1; 170 quad.callback = callback; 171 quad.started = false; 172 } 173 174 protected *quad_t new (&nichrome_t nichrome, void(&quad_t) callback) { 175 quad_t quad; 176 177 init (&quad, &nichrome, callback); 178 return &quad; 179 } 180 } 181} 182import Nichrome; 183import Nichrome::Box; 184import Nichrome::Label; 185import Nichrome::Button; 186import Nichrome::Quad; 187 188import Cairo; 189typedef real[3,3] m_t; 190typedef point_t[4] q_t; 191 192/* 193 * Ok, given an source quad and a dest rectangle, compute 194 * a transform that maps the rectangle to q. That's easier 195 * as the rectangle has some nice simple properties. Invert 196 * the matrix to find the opposite mapping 197 * 198 * q0 q1 199 * 200 * q3 q2 201 * 202 * | m00 m01 m02 | 203 * | m10 m11 m12 | 204 * | m20 m21 m22 | 205 * 206 * m [ 0 0 1 ] = q[0] 207 * 208 * Set m22 to 1, and solve: 209 * 210 * | m02 , m12 , 1 | = | q0x, q0y, 1 | 211 * 212 * | m00 * w + q0x m10 * w + q0y | 213 * | ------------- , ------------- , 1 | = | q1x, q1y, 1 | 214 * | m20 * w + 1 m20 * w + 1 | 215 216 * m00*w + q0x = q1x*(m20*w + 1) 217 * m00 = m20*q1x + (q1x - q0x) / w; 218 * 219 * m10*w + q0y = q1y*(m20*w + 1) 220 * m10 = m20*q1y + (q1y - q0y) / w; 221 * 222 * m01*h + q0x = q3x*(m21*h + 1) 223 * m01 = m21*q3x + (q3x - q0x) / h; 224 * 225 * m11*h + q0y = q3y*(m21*h + 1) 226 * m11 = m21*q3y + (q3y - q0y) / h 227 * 228 * m00*w + m01*h + q0x = q2x*(m20*w + m21*h + 1) 229 * 230 * m20*q1x*w + q1x - q0x + m21*q3x*h + q3x - q0x + q0x = m20*q2x*w + m21*q2x*h + q2x 231 * 232 * m20*q1x*w - m20*q2x*w = m21*q2x*h - m21*q3x*h + q2x - q1x + q0x - q3x + q0x - q0x 233 * 234 * m20*(q1x - q2x)*w = m21*(q2x - q3x)*h + q2x - q1x - q3x + q0x 235 * 236 * 237 * m10*w + m11*h + q0y = q2y*(m20*w + m21*h + 1) 238 * 239 * m20*q1y*w + q1y - q0y + m21*q3y*h + q3y - q0y + q0y = m20*q2y*w + m21*q2y*h + q2y 240 * 241 * m20*q1y*w - m20*q2y*w = m21*q2y*h - m21*q3y*h + q2y - q1y + q0y - q3y + q0y - q0y 242 * 243 * m20*(q1y - q2y)*w = m21*(q2y - q3y)*h + q2y - q1y - q3y + q0y 244 * 245 * 246 * m20*(q1x - q2x)*(q1y - q2y)*w = m21*(q2x - q3x)*(q1y - q2y)*h + (q2x - q1x - q3x + q0x)*(q1y - q2y) 247 * 248 * m20*(q1y - q2y)*(q1x - q2x)*w = m21*(q2y - q3y)*(q1x - q2x)*h + (q2y - q1y - q3y + q0y)*(q1x - q2x) 249 * 250 * 0 = m21*((q2x - q3x)*(q1y - q2y) - (q2y - q3y)*(q1x - q2x))*h + (stuff) 251 * = m21 * a + b; 252 * 253 * m21 = -(stuff) / (other stuff) 254 * 255 * m20 = f(m21) 256 * 257 * m00 = f(m20) 258 * m10 = f(m20) 259 * 260 * m01 = f(m21) 261 * m11 = f(m21) 262 * 263 * done. 264 */ 265m_t solve (q_t q, real w, real h) 266{ 267 real q0x = q[0].x, q0y = q[0].y; 268 real q1x = q[1].x, q1y = q[1].y; 269 real q2x = q[2].x, q2y = q[2].y; 270 real q3x = q[3].x, q3y = q[3].y; 271 real m00, m01, m02; 272 real m10, m11, m12; 273 real m20, m21, m22; 274 275 m02 = q0x; 276 m12 = q0y; 277 m22 = 1; 278 279 real a = ((q2x - q3x)*(q1y - q2y) - (q2y - q3y)*(q1x - q2x)) * h; 280 real b = (q2x - q1x - q3x + q0x) * (q1y - q2y) - (q2y - q1y - q3y + q0y) * (q1x - q2x); 281 m21 = - b / a; 282 283 if (q1x != q2x) 284 m20 = (m21 * (q2x - q3x) * h + q2x - q1x - q3x + q0x) / ((q1x - q2x) * w); 285 else 286 m20 = (m21 * (q2y - q3y) * h + q2y - q1y - q3y + q0y) / ((q1y - q2y) * w); 287 288 m00 = m20 * q1x + (q1x - q0x) / w; 289 m10 = m20 * q1y + (q1y - q0y) / w; 290 291 m01 = m21 * q3x + (q3x - q0x) / h; 292 m11 = m21 * q3y + (q3y - q0y) / h; 293 294 return (m_t) { 295 { m00, m01, m02 }, 296 { m10, m11, m12 }, 297 { m20, m21, m22 } }; 298} 299 300m_t 301invert (m_t m) 302{ 303 real det; 304 int i, j; 305 m_t r; 306 307 static int[3] a = { 2, 2, 1 }; 308 static int[3] b = { 1, 0, 0 }; 309 310 det = 0; 311 for (i = 0; i < 3; i++) { 312 real p; 313 int ai = a[i]; 314 int bi = b[i]; 315 p = m[i,0] * (m[ai,2] * m[bi,1] - m[ai,1] * m[bi,2]); 316 if (i == 1) 317 p = -p; 318 det += p; 319 } 320 det = 1/det; 321 for (j = 0; j < 3; j++) { 322 for (i = 0; i < 3; i++) { 323 real p; 324 int ai = a[i]; 325 int aj = a[j]; 326 int bi = b[i]; 327 int bj = b[j]; 328 329 p = m[ai,aj] * m[bi,bj] - m[ai,bj] * m[bi,aj]; 330 if (((i + j) & 1) != 0) 331 p = -p; 332 r[j,i] = det * p; 333 } 334 } 335 return r; 336} 337 338m_t 339rescale (m_t m, real limit) 340{ 341 real max = 0; 342 for (int j = 0; j < 3; j++) 343 for (int i = 0; i < 3; i++) 344 if ((real v = abs (m[j,i])) > max) 345 max = v; 346 real scale = limit / max; 347 for (int j = 0; j < 3; j++) 348 for (int i = 0; i < 3; i++) 349 m[j,i] *= scale; 350 return m; 351 352} 353 354string 355m_print (m_t m) 356{ 357 358 return sprintf ("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f", 359 m[0,0],m[0,1],m[0,2], 360 m[1,0],m[1,1],m[1,2], 361 m[2,0],m[2,1],m[2,2]); 362/* 363 return sprintf ("%v,%v,%v,%v,%v,%v,%v,%v,%v", 364 m[0,0],m[0,1],m[0,2], 365 m[1,0],m[1,1],m[1,2], 366 m[2,0],m[2,1],m[2,2]); 367*/ 368} 369 370int 371fixed (real x) 372{ 373 return floor (x * 65536 + 0.5) & 0xffffffff; 374} 375 376m_t 377to_fixed(m_t m) 378{ 379 m_t r; 380 381 for (int i = 0; i < 3; i++) 382 for (int j = 0; j < 3; j++) 383 r[i,j] = floor(m[i,j] * 65536 + 0.5) / 65536; 384 return r; 385} 386 387m_t 388error(m_t r, m_t f) 389{ 390 m_t e; 391 392 for (int i = 0; i < 3; i++) 393 for (int j = 0; j < 3; j++) { 394 real diff = abs(r[i,j] - f[i,j]); 395 if (r[i,j] != 0) 396 e[i,j] = diff / abs(r[i,j]); 397 else 398 e[i,j] = 0; 399 } 400 return e; 401} 402 403void 404m_print_fix (m_t m) 405{ 406 for (int i = 0; i < 3; i++) 407 { 408 printf (" { 0x%08x, 0x%08x, 0x%08x },\n", 409 fixed (m[i,0]), fixed (m[i,1]), fixed (m[i,2])); 410 } 411} 412 413real[2] 414map(m_t m, real x, real y) 415{ 416 real[3] in = { x, y, 1 }; 417 real[3] out = { 0 ... }; 418 419 for (int i = 0; i < 3; i++) { 420 for (int j = 0; j < 3; j++) 421 out[i] += m[i,j] * in[j]; 422 } 423 return (real[2]) { out[0] / out[2], out[1]/ out[2] }; 424} 425 426real 427position_error(m_t r, m_t f, real x, real y) 428{ 429 real[2] p_r = map(r, x, y); 430 real[2] p_f = map(f, x, y); 431 real error; 432 433 error = sqrt((p_r[0] - p_f[0]) ** 2 + (p_r[1] - p_f[1]) ** 2); 434 printf ("x: %g y: %g error %g\n", x, y, error); 435 printf ("\treal x: %g y: %g\n", p_r[0], p_r[1]); 436 printf ("\tfix x: %g y: %g\n", p_f[0], p_f[1]); 437 return error; 438} 439 440real 441max_error(m_t r, m_t f) { 442 real max = 0, max_x = 0, max_y = 0; 443 444 for (int x = 0; x <= 2560; x += 2560) 445 for (int y = 0; y <= 1600; y += 1600) { 446 real error = position_error(r, f, x, y); 447 if (error > max) { 448 max = error; 449 max_x = x; 450 max_y = y; 451 } 452 } 453 printf ("max error %g at %d, %d\n", max, max_x, max_y); 454 real[2] p_r = map(r, max_x ,max_y); 455 real[2] p_f = map(f, max_x, max_y); 456 printf ("\tdesired %7.2f, %7.2f actual %7.2f, %7.2f\n", 457 p_r[0], p_r[1], 458 p_f[0], p_f[1]); 459 return max; 460} 461 462string 463m_row (m_t m, int row) 464{ 465 return sprintf ("%10.5f %10.5f %10.5f", 466 m[row,0],m[row,1],m[row,2]); 467} 468 469Cairo::point_t[*] scale(Cairo::point_t[*] p, real w, real h) 470{ 471 for (int i = 0; i < dim (p); i++) { 472 p[i].x *= w; 473 p[i].y *= h; 474 } 475 return p; 476} 477 478typedef struct { 479 string name; 480 rect_t geometry; 481} output_t; 482 483autoload Process; 484 485 486output_t[*] get_outputs () { 487 output_t[...] outputs = {}; 488 twixt (file randr = Process::popen (Process::popen_direction.read, 489 false, "xrandr", "xrandr"); 490 File::close (randr)) 491 { 492 while (!File::end (randr)) { 493 string[*] words = String::wordsplit (File::fgets (randr), " "); 494 if (dim (words) >= 3 && words[1] == "connected") { 495 int geom = 2; 496 if (words[geom] == "primary") 497 geom++; 498 if (File::sscanf (words[geom], "%dx%d+%d+%d", 499 &(int width), &(int height), 500 &(int x), &(int y)) == 4) 501 { 502 outputs[dim(outputs)] = (output_t) { 503 name = words[0], 504 geometry = { 505 x = x, y = y, width = width, height = height 506 } 507 }; 508 } 509 } 510 } 511 } 512 return outputs; 513} 514 515void main () 516{ 517 m_t m = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }, m_i, m_r, m_f, m_e; 518 bool m_available = true; 519 output_t[*] outputs = get_outputs (); 520 output_t target_output; 521 522 if (dim (outputs) == 0) { 523 File::fprintf (stderr, "%s: No enabled outputs\n", argv[0]); 524 exit (1); 525 } 526 527 if (dim (argv) > 1) { 528 int i; 529 for (i = 0; i < dim (outputs); i++) 530 if (argv[1] == outputs[i].name) { 531 target_output = outputs[i]; 532 break; 533 } 534 if (i == dim (outputs)) { 535 File::fprintf (stderr, "%s: no enabled output \"%s\"\n", 536 argv[0], argv[1]); 537 exit (1); 538 } 539 } 540 else 541 target_output = outputs[0]; 542 543 real target_width = target_output.geometry.width; 544 real target_height = target_output.geometry.height; 545 546 real screen_width = 0; 547 real screen_height = 0; 548 549 for (int i = 0; i < dim (outputs); i++) 550 { 551 screen_width = max (screen_width, 552 outputs[i].geometry.x + 553 outputs[i].geometry.width); 554 screen_height = max (screen_height, 555 outputs[i].geometry.y + 556 outputs[i].geometry.height); 557 } 558 559 &nichrome_t nichrome = Nichrome::new ("Keystone Correction", 400, 350); 560 561 (*label_t)[3] label; 562 &label_t space = Label::new (&nichrome, ""); 563 for (int i = 0; i < 3; i++) { 564 label[i] = Label::new (&nichrome, "matrix"); 565 label[i]->font = "sans-9"; 566 } 567 568 void callback (&quad_t quad) { 569 real w = quad.geometry.width; 570 real h = quad.geometry.height; 571 string[3] text; 572 try { 573 m = solve (scale (quad.p, target_width / w, target_height / h), 574 target_width, target_height); 575 m_i = invert (m); 576 m_r = rescale (m_i, 16384); 577 for (int i = 0; i < 3; i++) 578 text[i] = m_row (m_i,i); 579 m_available = true; 580 } catch divide_by_zero (real a, real b) { 581 text = (string[3]) { "no solution", "" ... }; 582 m_available = false; 583 } 584 for (int i = 0; i < 3; i++) 585 Label::relabel (label[i], text[i]); 586 } 587 &quad_t quad = Quad::new (&nichrome, callback); 588 589 void doit_func (&widget_t widget, bool state) 590 { 591 if (m_available) 592 { 593 Process::system ("xrandr", 594 "xrandr", 595 "--fb", 596 sprintf ("%dx%d", screen_width, screen_height), 597 "--output", 598 target_output.name, 599 "--transform", 600 m_print (m_r)); 601 } 602 } 603 604 &button_t doit = Button::new (&nichrome, "doit", doit_func); 605 606 void show_func (&widget_t widget, bool state) 607 { 608 if (m_available) 609 { 610 printf ("normal: %s\n", m_print (m)); 611 printf ("inverse: %s\n", m_print (m_i)); 612 printf ("scaled: %s\n", m_print (m_r)); 613 m_f = to_fixed(m_r); 614 printf ("round: %s\n", m_print(m_f)); 615 printf ("fixed:\n"); 616 m_print_fix (m_i); 617 m_e = error(m_r, m_f); 618 printf ("error: %s\n", m_print (m_e)); 619 max_error(m_r, m_f); 620 } 621 } 622 623 &button_t show = Button::new (&nichrome, "show", show_func); 624 &button_t quit = Button::new (&nichrome, "quit", 625 void func (&widget_t w, bool state) { 626 w.nichrome.running = false; 627 }); 628 629 &box_t hbox = Box::new (Box::dir_t.horizontal, 630 Box::widget_item (&doit, 0), 631 Box::widget_item (&show, 0), 632 Box::widget_item (&quit, 0), 633 Box::glue_item (1)); 634 &box_t box = Box::new (Box::dir_t.vertical, 635 Box::box_item (&hbox), 636 Box::widget_item (label[0], 1, 0), 637 Box::widget_item (label[1], 1, 0), 638 Box::widget_item (label[2], 1, 0), 639 Box::widget_item (&space, 1, 0), 640 Box::widget_item (&quad, 1)); 641 Nichrome::set_box (&nichrome, &box); 642 Nichrome::main_loop (&nichrome); 643} 644 645main (); 646