keystone.5c revision 62df5ad0
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; 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 void text_at (cairo_t cr, point_t p, string text) { 53 text_extents_t e = text_extents (cr, text); 54 p.x = p.x - e.width / 2 - e.x_bearing; 55 p.y = p.y - e.height / 2 - e.y_bearing; 56 move_to (cr, p.x, p.y); 57 show_text (cr, text); 58 } 59 60 protected void draw (cairo_t cr, &quad_t quad) { 61 if (!quad.started) { 62 quad.p[2].x = quad.p[1].x = quad.geometry.width; 63 quad.p[3].y = quad.p[2].y = quad.geometry.height; 64 quad.started = true; 65 } 66 rectangle (cr, 0, 0, quad.geometry.width, quad.geometry.height); 67 set_source_rgba (cr, 0, 0, 0, .25); 68 fill (cr); 69 for (int i = 0; i < dim (quad.p); i++) 70 line_to (cr, quad.p[i].x, quad.p[i].y); 71 close_path (cr); 72 set_line_width (cr, quad.line_width); 73 set_source_rgba (cr, quad.line_color.red, quad.line_color.green, 74 quad.line_color.blue, quad.line_color.alpha); 75 set_line_join (cr, line_join_t.ROUND); 76 stroke (cr); 77 set_source_rgba (cr, quad.corner_color.red, quad.corner_color.green, 78 quad.corner_color.blue, quad.corner_color.alpha); 79 outline (cr, &quad); 80 fill (cr); 81 set_source_rgba (cr, 1, 1, 1, 1); 82 for (int i = 0; i < dim (quad.p); i++) 83 text_at (cr, quad.p[i], sprintf ("%d", i)); 84 } 85 86 int nearest (&quad_t quad, point_t p) { 87 real best_dist2 = 0; 88 int best = 0; 89 90 for (int i = 0; i < dim (quad.p); i++) { 91 real dist2 = ((p.x - quad.p[i].x) ** 2 + 92 (p.y - quad.p[i].y) ** 2); 93 if (i == 0 || dist2 < best_dist2) { 94 best_dist2 = dist2; 95 best = i; 96 } 97 } 98 return best; 99 } 100 101 protected void button (&quad_t quad, &button_event_t event) { 102 enum switch (event.type) { 103 case press: 104 quad.down = true; 105 quad.active = nearest (&quad, event); 106 break; 107 case release: 108 quad.down = false; 109 break; 110 default: 111 break; 112 } 113 } 114 115 protected void motion (&quad_t quad, &motion_event_t motion) { 116 if (quad.down) { 117 motion.x = max (0, min (quad.geometry.width, motion.x)); 118 motion.y = max (0, min (quad.geometry.height, motion.y)); 119 quad.p[quad.active].x = motion.x; 120 quad.p[quad.active].y = motion.y; 121 quad.callback (&quad); 122 Widget::reoutline (&quad); 123 Widget::redraw (&quad); 124 } 125 } 126 127 protected void configure (&quad_t quad, 128 rect_t geometry) 129 { 130 if (quad.geometry.width > 0 && quad.geometry.height > 0) 131 { 132 real x_scale = geometry.width / quad.geometry.width; 133 real y_scale = geometry.height / quad.geometry.height; 134 for (int i = 0; i< 4; i++) { 135 quad.p[i].x *= x_scale; 136 quad.p[i].y *= y_scale; 137 } 138 } 139 Widget::configure (&quad, geometry); 140 quad.callback (&quad); 141 } 142 143 protected void init (&quad_t quad, 144 &nichrome_t nichrome, 145 void (&quad_t) callback) { 146 Widget::init (&nichrome, &quad); 147 quad.outline = outline; 148 quad.draw = draw; 149 quad.button = button; 150 quad.motion = motion; 151 quad.configure = configure; 152 quad.p = (point_t[4]) { 153 { x = 0, y = 0 } ... 154 }; 155 quad.line_color = (rgba_color_t) { 156 red = 1, green = 0, blue = 0, alpha = .5 157 }; 158 quad.line_width = 10; 159 quad.corner_color = (rgba_color_t) { 160 red = 0, green = 0, blue = 1, alpha = 0.75 161 }; 162 quad.corner_diameter = 20; 163 quad.down = false; 164 quad.active = -1; 165 quad.callback = callback; 166 quad.started = false; 167 } 168 169 protected *quad_t new (&nichrome_t nichrome, void(&quad_t) callback) { 170 quad_t quad; 171 172 init (&quad, &nichrome, callback); 173 return &quad; 174 } 175 } 176} 177import Nichrome; 178import Nichrome::Box; 179import Nichrome::Label; 180import Nichrome::Button; 181import Nichrome::Quad; 182 183import Cairo; 184typedef real[3,3] m_t; 185typedef point_t[4] q_t; 186 187/* 188 * Ok, given an source quad and a dest rectangle, compute 189 * a transform that maps the rectangle to q. That's easier 190 * as the rectangle has some nice simple properties. Invert 191 * the matrix to find the opposite mapping 192 * 193 * q0 q1 194 * 195 * q3 q2 196 * 197 * | m00 m01 m02 | 198 * | m10 m11 m12 | 199 * | m20 m21 m22 | 200 * 201 * m [ 0 0 1 ] = q[0] 202 * 203 * Set m22 to 1, and solve: 204 * 205 * | m02 , m12 , 1 | = | q0x, q0y, 1 | 206 * 207 * | m00 * w + q0x m10 * w + q0y | 208 * | ------------- , ------------- , 1 | = | q1x, q1y, 1 | 209 * | m20 * w + 1 m20 * w + 1 | 210 211 * m00*w + q0x = q1x*(m20*w + 1) 212 * m00 = m20*q1x + (q1x - q0x) / w; 213 * 214 * m10*w + q0y = q1y*(m20*w + 1) 215 * m10 = m20*q1y + (q1y - q0y) / w; 216 * 217 * m01*h + q0x = q3x*(m21*h + 1) 218 * m01 = m21*q3x + (q3x - q0x) / h; 219 * 220 * m11*h + q0y = q3y*(m21*h + 1) 221 * m11 = m21*q3y + (q3y - q0y) / h 222 * 223 * m00*w + m01*h + q0x = q2x*(m20*w + m21*h + 1) 224 * 225 * m20*q1x*w + q1x - q0x + m21*q3x*h + q3x - q0x + q0x = m20*q2x*w + m21*q2x*h + q2x 226 * 227 * m20*q1x*w - m20*q2x*w = m21*q2x*h - m21*q3x*h + q2x - q1x + q0x - q3x + q0x - q0x 228 * 229 * m20*(q1x - q2x)*w = m21*(q2x - q3x)*h + q2x - q1x - q3x + q0x 230 * 231 * 232 * m10*w + m11*h + q0y = q2y*(m20*w + m21*h + 1) 233 * 234 * m20*q1y*w + q1y - q0y + m21*q3y*h + q3y - q0y + q0y = m20*q2y*w + m21*q2y*h + q2y 235 * 236 * m20*q1y*w - m20*q2y*w = m21*q2y*h - m21*q3y*h + q2y - q1y + q0y - q3y + q0y - q0y 237 * 238 * m20*(q1y - q2y)*w = m21*(q2y - q3y)*h + q2y - q1y - q3y + q0y 239 * 240 * 241 * m20*(q1x - q2x)*(q1y - q2y)*w = m21*(q2x - q3x)*(q1y - q2y)*h + (q2x - q1x - q3x + q0x)*(q1y - q2y) 242 * 243 * m20*(q1y - q2y)*(q1x - q2x)*w = m21*(q2y - q3y)*(q1x - q2x)*h + (q2y - q1y - q3y + q0y)*(q1x - q2x) 244 * 245 * 0 = m21*((q2x - q3x)*(q1y - q2y) - (q2y - q3y)*(q1x - q2x))*h + (stuff) 246 * = m21 * a + b; 247 * 248 * m21 = -(stuff) / (other stuff) 249 * 250 * m20 = f(m21) 251 * 252 * m00 = f(m20) 253 * m10 = f(m20) 254 * 255 * m01 = f(m21) 256 * m11 = f(m21) 257 * 258 * done. 259 */ 260m_t solve (q_t q, real w, real h) 261{ 262 real q0x = q[0].x, q0y = q[0].y; 263 real q1x = q[1].x, q1y = q[1].y; 264 real q2x = q[2].x, q2y = q[2].y; 265 real q3x = q[3].x, q3y = q[3].y; 266 real m00, m01, m02; 267 real m10, m11, m12; 268 real m20, m21, m22; 269 270 m02 = q0x; 271 m12 = q0y; 272 m22 = 1; 273 274 real a = ((q2x - q3x)*(q1y - q2y) - (q2y - q3y)*(q1x - q2x)) * h; 275 real b = (q2x - q1x - q3x + q0x) * (q1y - q2y) - (q2y - q1y - q3y + q0y) * (q1x - q2x); 276 m21 = - b / a; 277 278 if (q1x != q2x) 279 m20 = (m21 * (q2x - q3x) * h + q2x - q1x - q3x + q0x) / ((q1x - q2x) * w); 280 else 281 m20 = (m21 * (q2y - q3y) * h + q2y - q1y - q3y + q0y) / ((q1y - q2y) * w); 282 283 m00 = m20 * q1x + (q1x - q0x) / w; 284 m10 = m20 * q1y + (q1y - q0y) / w; 285 286 m01 = m21 * q3x + (q3x - q0x) / h; 287 m11 = m21 * q3y + (q3y - q0y) / h; 288 289 return (m_t) { 290 { m00, m01, m02 }, 291 { m10, m11, m12 }, 292 { m20, m21, m22 } }; 293} 294 295m_t 296invert (m_t m) 297{ 298 real det; 299 int i, j; 300 m_t r; 301 302 static int[3] a = { 2, 2, 1 }; 303 static int[3] b = { 1, 0, 0 }; 304 305 det = 0; 306 for (i = 0; i < 3; i++) { 307 real p; 308 int ai = a[i]; 309 int bi = b[i]; 310 p = m[i,0] * (m[ai,2] * m[bi,1] - m[ai,1] * m[bi,2]); 311 if (i == 1) 312 p = -p; 313 det += p; 314 } 315 det = 1/det; 316 for (j = 0; j < 3; j++) { 317 for (i = 0; i < 3; i++) { 318 real p; 319 int ai = a[i]; 320 int aj = a[j]; 321 int bi = b[i]; 322 int bj = b[j]; 323 324 p = m[ai,aj] * m[bi,bj] - m[ai,bj] * m[bi,aj]; 325 if (((i + j) & 1) != 0) 326 p = -p; 327 r[j,i] = det * p; 328 } 329 } 330 return r; 331} 332 333m_t 334rescale (m_t m, real limit) 335{ 336 real max = 0; 337 for (int j = 0; j < 3; j++) 338 for (int i = 0; i < 3; i++) 339 if ((real v = abs (m[j,i])) > max) 340 max = v; 341 real scale = limit / max; 342 for (int j = 0; j < 3; j++) 343 for (int i = 0; i < 3; i++) 344 m[j,i] *= scale; 345 return m; 346 347} 348 349string 350m_print (m_t m) 351{ 352 /* 353 return sprintf ("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f", 354 m[0,0],m[0,1],m[0,2], 355 m[1,0],m[1,1],m[1,2], 356 m[2,0],m[2,1],m[2,2]); 357 */ 358 return sprintf ("%v,%v,%v,%v,%v,%v,%v,%v,%v", 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 364int 365fixed (real x) 366{ 367 return floor (x * 65536 + 0.5) & 0xffffffff; 368} 369 370void 371m_print_fix (m_t m) 372{ 373 for (int i = 0; i < 3; i++) 374 { 375 printf (" { 0x%08x, 0x%08x, 0x%08x },\n", 376 fixed (m[i,0]), fixed (m[i,1]), fixed (m[i,2])); 377 } 378} 379 380string 381m_row (m_t m, int row) 382{ 383 return sprintf ("%10.5f %10.5f %10.5f", 384 m[row,0],m[row,1],m[row,2]); 385} 386 387Cairo::point_t[*] scale(Cairo::point_t[*] p, real w, real h) 388{ 389 for (int i = 0; i < dim (p); i++) { 390 p[i].x *= w; 391 p[i].y *= h; 392 } 393 return p; 394} 395 396typedef struct { 397 string name; 398 rect_t geometry; 399} output_t; 400 401autoload Process; 402 403 404output_t[*] get_outputs () { 405 output_t[...] outputs = {}; 406 twixt (file randr = Process::popen (Process::popen_direction.read, 407 false, "xrandr", "xrandr"); 408 File::close (randr)) 409 { 410 while (!File::end (randr)) { 411 string[*] words = String::wordsplit (File::fgets (randr), " "); 412 if (dim (words) >= 3 && words[1] == "connected" && 413 File::sscanf (words[2], "%dx%d+%d+%d", 414 &(int width), &(int height), 415 &(int x), &(int y)) == 4) 416 { 417 outputs[dim(outputs)] = (output_t) { 418 name = words[0], 419 geometry = { 420 x = x, y = y, width = width, height = height 421 } 422 }; 423 } 424 } 425 } 426 return outputs; 427} 428 429void main () 430{ 431 m_t m = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }, m_i, m_r; 432 bool m_available = true; 433 output_t[*] outputs = get_outputs (); 434 output_t target_output; 435 436 if (dim (outputs) == 0) { 437 File::fprintf (stderr, "%s: No enabled outputs\n", argv[0]); 438 exit (1); 439 } 440 441 if (dim (argv) > 1) { 442 int i; 443 for (i = 0; i < dim (outputs); i++) 444 if (argv[1] == outputs[i].name) { 445 target_output = outputs[i]; 446 break; 447 } 448 if (i == dim (outputs)) { 449 File::fprintf (stderr, "%s: no enabled output \"%s\"\n", 450 argv[0], argv[1]); 451 exit (1); 452 } 453 } 454 else 455 target_output = outputs[0]; 456 457 real target_width = target_output.geometry.width; 458 real target_height = target_output.geometry.height; 459 460 real screen_width = 0; 461 real screen_height = 0; 462 463 for (int i = 0; i < dim (outputs); i++) 464 { 465 screen_width = max (screen_width, 466 outputs[i].geometry.x + 467 outputs[i].geometry.width); 468 screen_height = max (screen_height, 469 outputs[i].geometry.y + 470 outputs[i].geometry.height); 471 } 472 473 &nichrome_t nichrome = Nichrome::new ("Keystone Correction", 400, 350); 474 475 (*label_t)[3] label; 476 &label_t space = Label::new (&nichrome, ""); 477 for (int i = 0; i < 3; i++) { 478 label[i] = Label::new (&nichrome, "matrix"); 479 label[i]->font = "sans-9"; 480 } 481 482 void callback (&quad_t quad) { 483 real w = quad.geometry.width; 484 real h = quad.geometry.height; 485 string[3] text; 486 try { 487 m = solve (scale (quad.p, target_width / w, target_height / h), 488 target_width, target_height); 489 m_i = invert (m); 490 m_r = rescale (m_i, 16384); 491 for (int i = 0; i < 3; i++) 492 text[i] = m_row (m_i,i); 493 m_available = true; 494 } catch divide_by_zero (real a, real b) { 495 text = (string[3]) { "no solution", "" ... }; 496 m_available = false; 497 } 498 for (int i = 0; i < 3; i++) 499 Label::relabel (label[i], text[i]); 500 } 501 &quad_t quad = Quad::new (&nichrome, callback); 502 503 void doit_func (&widget_t widget, bool state) 504 { 505 if (m_available) 506 { 507 Process::system ("xrandr", 508 "xrandr", 509 "--fb", 510 sprintf ("%dx%d", screen_width, screen_height), 511 "--output", 512 target_output.name, 513 "--transform", 514 m_print (m_r)); 515 } 516 } 517 518 &button_t doit = Button::new (&nichrome, "doit", doit_func); 519 520 void show_func (&widget_t widget, bool state) 521 { 522 if (m_available) 523 { 524 printf ("normal: %s\n", m_print (m)); 525 printf ("inverse: %s\n", m_print (m_i)); 526 printf ("scaled: %s\n", m_print (m_r)); 527 printf ("fixed:\n"); 528 m_print_fix (m_i); 529 } 530 } 531 532 &button_t show = Button::new (&nichrome, "show", show_func); 533 &button_t quit = Button::new (&nichrome, "quit", 534 void func (&widget_t w, bool state) { 535 w.nichrome.running = false; 536 }); 537 538 &box_t hbox = Box::new (Box::dir_t.horizontal, 539 Box::widget_item (&doit, 0), 540 Box::widget_item (&show, 0), 541 542 Box::widget_item (&quit, 0), 543 Box::glue_item (1)); 544 &box_t box = Box::new (Box::dir_t.vertical, 545 Box::box_item (&hbox), 546 Box::widget_item (label[0], 0), 547 Box::widget_item (label[1], 0), 548 Box::widget_item (label[2], 0), 549 Box::widget_item (&space, 0), 550 Box::widget_item (&quad, 1)); 551 Nichrome::set_box (&nichrome, &box); 552 Nichrome::main_loop (&nichrome); 553} 554 555main (); 556