keystone.5c revision 966b534a
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, 256, 256); 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 369int 370fixed (real x) 371{ 372 return floor (x * 65536 + 0.5) & 0xffffffff; 373} 374 375void 376m_print_fix (m_t m) 377{ 378 for (int i = 0; i < 3; i++) 379 { 380 printf (" { 0x%08x, 0x%08x, 0x%08x },\n", 381 fixed (m[i,0]), fixed (m[i,1]), fixed (m[i,2])); 382 } 383} 384 385string 386m_row (m_t m, int row) 387{ 388 return sprintf ("%10.5f %10.5f %10.5f", 389 m[row,0],m[row,1],m[row,2]); 390} 391 392Cairo::point_t[*] scale(Cairo::point_t[*] p, real w, real h) 393{ 394 for (int i = 0; i < dim (p); i++) { 395 p[i].x *= w; 396 p[i].y *= h; 397 } 398 return p; 399} 400 401typedef struct { 402 string name; 403 rect_t geometry; 404} output_t; 405 406autoload Process; 407 408 409output_t[*] get_outputs () { 410 output_t[...] outputs = {}; 411 twixt (file randr = Process::popen (Process::popen_direction.read, 412 false, "xrandr", "xrandr"); 413 File::close (randr)) 414 { 415 while (!File::end (randr)) { 416 string[*] words = String::wordsplit (File::fgets (randr), " "); 417 if (dim (words) >= 3 && words[1] == "connected" && 418 File::sscanf (words[2], "%dx%d+%d+%d", 419 &(int width), &(int height), 420 &(int x), &(int y)) == 4) 421 { 422 outputs[dim(outputs)] = (output_t) { 423 name = words[0], 424 geometry = { 425 x = x, y = y, width = width, height = height 426 } 427 }; 428 } 429 } 430 } 431 return outputs; 432} 433 434void main () 435{ 436 m_t m = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }, m_i, m_r; 437 bool m_available = true; 438 output_t[*] outputs = get_outputs (); 439 output_t target_output; 440 441 if (dim (outputs) == 0) { 442 File::fprintf (stderr, "%s: No enabled outputs\n", argv[0]); 443 exit (1); 444 } 445 446 if (dim (argv) > 1) { 447 int i; 448 for (i = 0; i < dim (outputs); i++) 449 if (argv[1] == outputs[i].name) { 450 target_output = outputs[i]; 451 break; 452 } 453 if (i == dim (outputs)) { 454 File::fprintf (stderr, "%s: no enabled output \"%s\"\n", 455 argv[0], argv[1]); 456 exit (1); 457 } 458 } 459 else 460 target_output = outputs[0]; 461 462 real target_width = target_output.geometry.width; 463 real target_height = target_output.geometry.height; 464 465 real screen_width = 0; 466 real screen_height = 0; 467 468 for (int i = 0; i < dim (outputs); i++) 469 { 470 screen_width = max (screen_width, 471 outputs[i].geometry.x + 472 outputs[i].geometry.width); 473 screen_height = max (screen_height, 474 outputs[i].geometry.y + 475 outputs[i].geometry.height); 476 } 477 478 &nichrome_t nichrome = Nichrome::new ("Keystone Correction", 400, 350); 479 480 (*label_t)[3] label; 481 &label_t space = Label::new (&nichrome, ""); 482 for (int i = 0; i < 3; i++) { 483 label[i] = Label::new (&nichrome, "matrix"); 484 label[i]->font = "sans-9"; 485 } 486 487 void callback (&quad_t quad) { 488 real w = quad.geometry.width; 489 real h = quad.geometry.height; 490 string[3] text; 491 try { 492 m = solve (scale (quad.p, target_width / w, target_height / h), 493 target_width, target_height); 494 m_i = invert (m); 495 m_r = rescale (m_i, 16384); 496 for (int i = 0; i < 3; i++) 497 text[i] = m_row (m_i,i); 498 m_available = true; 499 } catch divide_by_zero (real a, real b) { 500 text = (string[3]) { "no solution", "" ... }; 501 m_available = false; 502 } 503 for (int i = 0; i < 3; i++) 504 Label::relabel (label[i], text[i]); 505 } 506 &quad_t quad = Quad::new (&nichrome, callback); 507 508 void doit_func (&widget_t widget, bool state) 509 { 510 if (m_available) 511 { 512 Process::system ("xrandr", 513 "xrandr", 514 "--fb", 515 sprintf ("%dx%d", screen_width, screen_height), 516 "--output", 517 target_output.name, 518 "--transform", 519 m_print (m_r)); 520 } 521 } 522 523 &button_t doit = Button::new (&nichrome, "doit", doit_func); 524 525 void show_func (&widget_t widget, bool state) 526 { 527 if (m_available) 528 { 529 printf ("normal: %s\n", m_print (m)); 530 printf ("inverse: %s\n", m_print (m_i)); 531 printf ("scaled: %s\n", m_print (m_r)); 532 printf ("fixed:\n"); 533 m_print_fix (m_i); 534 } 535 } 536 537 &button_t show = Button::new (&nichrome, "show", show_func); 538 &button_t quit = Button::new (&nichrome, "quit", 539 void func (&widget_t w, bool state) { 540 w.nichrome.running = false; 541 }); 542 543 &box_t hbox = Box::new (Box::dir_t.horizontal, 544 Box::widget_item (&doit, 0), 545 Box::widget_item (&show, 0), 546 Box::widget_item (&quit, 0), 547 Box::glue_item (1)); 548 &box_t box = Box::new (Box::dir_t.vertical, 549 Box::box_item (&hbox), 550 Box::widget_item (label[0], 1, 0), 551 Box::widget_item (label[1], 1, 0), 552 Box::widget_item (label[2], 1, 0), 553 Box::widget_item (&space, 1, 0), 554 Box::widget_item (&quad, 1)); 555 Nichrome::set_box (&nichrome, &box); 556 Nichrome::main_loop (&nichrome); 557} 558 559main (); 560