keystone.5c 14 KB

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