1 /** 2 Module implements on-screen image plotting utilities. 3 4 DCV offers simple interface to show an image on screen: 5 6 ---- 7 Image image = imread("image.png"); 8 9 // Simply, show the image 10 image.imshow(); 11 12 // Optionally, show the image on the figure with given title: 13 image.imshow("Some Image"); 14 15 // ... or do some processing, then show it in-line 16 image 17 .sliced 18 .as!float 19 .slice 20 .conv!symmetric(gaussian!float(1.0f, 3, 3)) 21 .imshow; 22 23 // ... or instantiate new figure to setup some useful callbacks, than use the 24 // Figure interface to draw on it's canvas, and show it: 25 26 auto f = figure("Figure title"); // create the figure with given title 27 28 // set the mouse move callback 29 f.setCursorCallback( (Figure figure, double x, double y) 30 { 31 writeln("Mouse moved to: ", [x, y]); 32 }) 33 34 // set the mouse button click callback 35 f.setMouseCallback( (Figure figure, int button, int scancode, int mods) 36 { 37 writeln("Mouse clicked: ", [button, scancode, mods]); 38 }); 39 40 f.draw(image); // draw an image to the figure's canvas. 41 f.show(); // show the figure on screen. 42 43 // Once figure's image buffer is drawn out (say you have an image, and few plots drawn on it), 44 // it can be extracted from the figure, and used in rest of the code: 45 Image plotImage = figure(title).image; 46 plotImage.imwrite("my_plot.png"); 47 48 // And at the end, you can run the event loop for each previously set up figure, to wait 49 // for key input, or given time to pass. 50 waitKey!"seconds"(10); 51 ---- 52 Figure mechanism is integrated with ggplotd library, so GGPlotD context can be directly plotted onto existing figure. 53 To use GGPlotD library integration with DCV $(LINK2 http://dcv.dlang.io/?loc=dcv_plot_figure.html#plo, (dcv.plot.figure.plot)), 54 define ggplotd subConfiguration of dcv in dub configuration file: 55 ---- 56 "dependencies": { 57 "dcv": "~>0.1.2" 58 }, 59 "subConfigurations":{ 60 "dcv": "ggplotd" 61 } 62 ---- 63 This configuration is actually in dcv:plot subpackage, so if you define dcv:plot as dependency, you should define your subConfigurations as: 64 ---- 65 "dependencies": { 66 "dcv:plot": "~>0.1.2" 67 }, 68 "subConfigurations":{ 69 "dcv:plot": "ggplotd" 70 } 71 ---- 72 Example: 73 ---- 74 immutable title = "Image With Point Plot"; 75 // show the image 76 image.imshow(title); 77 // construct the plot 78 auto gg = GGPlotD().put(geomPoint(Aes!(double[], "x", double[], "y")([100.00, 200.0], [200.0,100.0]))); 79 // draw it onto the figure with given title... 80 gg.plot(title); 81 ---- 82 83 $(DL Module contains: 84 $(DD 85 $(LINK2 #imshow,imshow) 86 $(LINK2 #plot,plot) 87 $(LINK2 #waitKey,waitKey) 88 $(LINK2 #imdestroy,imdestroy) 89 $(LINK2 #Figure,Figure) 90 ) 91 ) 92 93 Copyright: Copyright Relja Ljubobratovic 2016. 94 95 Authors: Relja Ljubobratovic 96 97 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 98 */ 99 100 module dcv.plot.figure; 101 102 import std..string : toStringz; 103 import std.exception; 104 import std.conv : to; 105 106 import mir.ndslice.slice; 107 108 version(ggplotd) 109 { 110 import ggplotd.ggplotd, ggplotd.aes, ggplotd.axes, ggplotd.geom; 111 } 112 113 import dcv.core.image : Image, ImageFormat, BitDepth, asImage; 114 import dcv.plot.bindings; 115 116 117 /** 118 Exception thrown if drawing context hasn't been properly initialized. 119 120 Note: 121 Current implementation of the module utilizes glfw3 library as drawing backend. 122 API calls in the module may throw this exception if glfwInit fails at the module 123 initialization. 124 125 See: 126 http://www.glfw.org/docs/latest/group__init.html#ga317aac130a235ab08c6db0834907d85e 127 */ 128 class ContextNotInitialized : Exception 129 { 130 this() 131 { 132 super("Drawing context has not been initialized properly."); 133 } 134 } 135 136 /** 137 Create a plotting figure. 138 139 Params: 140 title = Title of the window. If none given (default), window is named by "Figure id". 141 142 Throws: 143 ContextNotInitialized 144 145 Returns: 146 If figure with given title exists already, that figure is returned, 147 otherwise new figure is created and returned. 148 */ 149 Figure figure(string title = "") 150 { 151 mixin(checkContextInit); 152 153 Figure f = null; 154 if (title == "") 155 title = "Figure " ~ _figures.length.to!string; 156 foreach (e; _figures) 157 { 158 if (e.title == title) 159 { 160 f = e; 161 break; 162 } 163 } 164 if (f is null) 165 { 166 f = new Figure(title); 167 if (_figures.length != 0) 168 { 169 auto p = _figures[$ - 1].position; 170 immutable typeof(p[0]) offset = 30; 171 // TODO: figure out smarter window cascading. 172 f.moveTo(p[0] + offset, p[1] + offset); 173 } 174 175 _figures ~= f; 176 } 177 return f; 178 } 179 180 /** 181 Show an image to screen. 182 183 Params: 184 image = Image that is to be shown on the screen. 185 title = Title of the window. If none given (default), window is named by "Figure id". 186 187 If figure with given title exists, than the image content is updated with the given image. 188 189 Throws: 190 ContextNotInitialized 191 192 Returns: 193 If figure with given title exists already, that figure is returned, 194 otherwise new figure is created and returned. 195 */ 196 Figure imshow(Image image, string title = "") 197 { 198 auto f = figure(title); 199 f.draw(image); 200 f.show(); 201 return f; 202 } 203 204 /// ditto 205 Figure imshow(SliceKind kind, size_t[] packs, Iterator) 206 (Slice!(kind, packs, Iterator) slice, string title = "") 207 { 208 auto f = figure(title); 209 f.draw(slice, ImageFormat.IF_UNASSIGNED); 210 f.show(); 211 return f; 212 } 213 214 /// ditto 215 Figure imshow(SliceKind kind, size_t[] packs, Iterator) 216 (Slice!(kind, packs, Iterator) slice, ImageFormat format, string title = "") 217 { 218 auto f = figure(title); 219 f.draw(slice, format); 220 f.show(); 221 return f; 222 } 223 224 version(ggplotd) 225 { 226 /** 227 Show given image, and then plot given GGPlotD context on top of it. 228 229 Params: 230 image = Image that is to be shown on the screen. 231 gg = Plotted data on top of the image. 232 title = Title of the window. If none given (default), window is named by "Figure id". 233 234 Throws: 235 ContextNotInitialized 236 237 Returns: 238 If figure with given title exists already, that figure is returned, 239 otherwise new figure is created and returned. 240 */ 241 Figure plot(Image image, GGPlotD gg, string title = "") 242 { 243 auto f = figure(title); 244 f.draw(image); 245 f.draw(gg); 246 f.show(); 247 return f; 248 } 249 250 /// ditto 251 Figure plot(SliceKind kind, size_t[] packs, Iterator) 252 (Slice!(kind, packs, Iterator) slice, GGPlotD gg, string title = "") 253 { 254 auto f = figure(title); 255 f.draw(slice, ImageFormat.IF_UNASSIGNED); 256 f.draw(gg); 257 f.show(); 258 return f; 259 } 260 261 /// ditto 262 Figure plot(SliceKind kind, size_t[] packs, Iterator) 263 (Slice!(kind, packs, Iterator) slice, ImageFormat format, GGPlotD gg, string title = "") 264 { 265 auto f = figure(title); 266 f.draw(slice, format); 267 f.draw(gg); 268 f.show(); 269 return f; 270 } 271 272 /** 273 Plot GGPlotD context onto figure with given title. 274 275 Given plot is drawn on top of figure's current image buffer. Size of the figure, and it's image buffer is 276 unchanged. If no figure exists with given title, new one is allocated with default setup (500x500, with 277 black background), and the plot is drawn on it. 278 279 Params: 280 gg = GGPlotD context, to be plotted on figure. 281 title = Title of the window. If none given (default), window is named by "Figure id". 282 283 Throws: 284 ContextNotInitialized 285 286 Returns: 287 If figure with given title exists already, that figure is returned, 288 otherwise new figure is created and returned. 289 */ 290 Figure plot(GGPlotD gg, string title = "") 291 { 292 auto f = figure(title); 293 f.draw(gg); 294 f.show(); 295 return f; 296 } 297 } 298 299 /** 300 Run the event loop for each present figure, and wait for key and/or given time. 301 302 Params: 303 unit = Unit in which time count is given. Same as core.time.Duration unit parameters. 304 count = Number of unit ticks to wait for event loop to finish. If left at zero (default), runs indefinitelly. 305 306 Throws: 307 ContextNotInitialized 308 309 Returns: 310 Ascii value as int of keyboard press, or 0 if timer runs out. 311 */ 312 int waitKey(string unit = "msecs")(ulong count = 0) 313 { 314 import std.datetime : StopWatch; 315 316 mixin(checkContextInit); 317 318 StopWatch stopwatch; 319 stopwatch.start; 320 321 _lastKey = -1; 322 auto hiddenLoopCheck = 0; 323 324 while (true) 325 { 326 if (count && count < mixin("stopwatch.peek." ~ unit)) 327 break; 328 329 glfwPollEvents(); 330 331 if (_lastKey != -1) 332 return _lastKey; 333 334 bool allHidden = true; 335 336 foreach (f; _figures) 337 { 338 auto glfwWindow = f._glfwWindow; 339 340 if (f.visible == false) 341 { 342 continue; 343 } 344 345 if (glfwWindowShouldClose(glfwWindow)) 346 { 347 f.hide(); 348 continue; 349 } 350 351 allHidden = false; 352 f.render(); 353 354 } 355 356 if (allHidden) 357 { 358 /* 359 TODO: think this through - its good behavior to end the event loop 360 when no window is opened, but if image is shown right before the 361 waitKey call, glfw doesn't actually show the window, so Figure.visible 362 returns false. 363 364 To bypass this, count event loop calls where all windows are hidden, 365 and if counter reaches enough hits (say 100), break the loop. 366 367 This is temporary solution. 368 */ 369 if (++hiddenLoopCheck > 100) 370 break; 371 } 372 } 373 374 return 0; 375 } 376 377 /** 378 Destroy figure. 379 380 Params: 381 title = Title of the window to be destroyed. If left as empty string, destroys all windows. 382 383 Throws: 384 ContextNotInitialized 385 */ 386 void imdestroy(string title = "") 387 { 388 mixin(checkContextInit); 389 390 if (title == "") 391 { 392 foreach (f; _figures) 393 { 394 f.hide(); 395 destroy(f); 396 } 397 _figures = []; 398 } 399 else 400 { 401 import std.algorithm.mutation : remove; 402 403 foreach (i, f; _figures) 404 { 405 if (f.title == title) 406 { 407 f.hide(); 408 destroy(f); 409 _figures.remove(i); 410 break; 411 } 412 } 413 } 414 } 415 416 /// Key press callback function. 417 alias KeyPressCallback = void delegate(int key, int scancode, int action, int mods); 418 /// Character callback function. 419 alias CharCallback = void delegate(uint key); 420 421 /** 422 Assign key press callback function. 423 */ 424 void setKeyPressCallback(KeyPressCallback clbck) 425 { 426 _keyPressCallback = clbck; 427 } 428 429 /** 430 Assign character input callback function. 431 */ 432 void setCharCallback(CharCallback clbck) 433 { 434 _charCallback = clbck; 435 } 436 437 /** 438 Plotting figure type. 439 */ 440 class Figure 441 { 442 /// Mouse button callback function. 443 alias MouseCallback = void delegate(Figure figure, int button, int action, int mods); 444 /// Cursor movement callback function. 445 alias CursorCallback = void delegate(Figure figure, double xpos, double ypos); 446 /// Figure closing callback function. 447 alias CloseCallback = void delegate(Figure figure); 448 449 private 450 { 451 GLFWwindow* _glfwWindow = null; 452 453 int _width = 0; 454 int _height = 0; 455 ubyte[] _data = void; 456 string _title = ""; 457 458 MouseCallback _mouseCallback = null; 459 CursorCallback _cursorCallback = null; 460 CloseCallback _closeCallback = null; 461 } 462 463 @disable this(); 464 465 private void setupCallbacks() 466 { 467 glfwSetInputMode(_glfwWindow, GLFW_STICKY_KEYS, 1); 468 469 glfwSetMouseButtonCallback(_glfwWindow, &mouseCallbackWrapper); 470 glfwSetCursorPosCallback(_glfwWindow, &cursorCallbackWrapper); 471 glfwSetWindowCloseCallback(_glfwWindow, &closeCallbackWrapper); 472 glfwSetCharCallback(_glfwWindow, &charCallbackWrapper); 473 glfwSetKeyCallback(_glfwWindow, &keyCallbackWrapper); 474 475 setCloseCallback(&defaultCloseCallback); 476 } 477 478 /// Construct figure window with given title. 479 private this(string title, int width = 512, int height = 512) 480 in 481 { 482 assert(width > 0); 483 assert(height > 0); 484 } 485 body 486 { 487 _title = title; 488 _width = width; 489 _height = height; 490 _data = new ubyte[_width * _height * 3]; 491 492 setupWindow(); 493 fitWindow(); 494 495 setupCallbacks(); 496 } 497 498 /// Construct figure window with given title, and fill it with given image. 499 private this(string title, Image image) 500 in 501 { 502 assert(image !is null); 503 assert(!image.empty); 504 } 505 body 506 { 507 this(title, cast(int)image.width, cast(int)image.height); 508 draw(image); 509 } 510 511 /// Construct figure window with given title, and fill it with given image. 512 private this(SliceKind kind, size_t[] packs, Iterator) 513 (string title, Slice!(kind, packs, Iterator) slice, ImageFormat format = ImageFormat.IF_UNASSIGNED) 514 if (N == 2 || N == 3) 515 { 516 this(title, cast(int)slice.length!1, cast(int)slice.length!0); 517 draw(slice, format); 518 } 519 520 ~this() 521 { 522 if (_glfwWindow !is null) 523 { 524 glfwDestroyWindow(_glfwWindow); 525 } 526 } 527 528 /// Assign mouse callback function. 529 Figure setMouseCallback(MouseCallback clbck) 530 { 531 _mouseCallback = clbck; 532 return this; 533 } 534 535 Figure setCursorCallback(CursorCallback clbck) 536 { 537 _cursorCallback = clbck; 538 return this; 539 } 540 541 Figure setCloseCallback(CloseCallback clbck) 542 { 543 _closeCallback = clbck; 544 return this; 545 } 546 547 @property width() inout 548 { 549 return _width; 550 } 551 552 @property height() inout 553 { 554 return _height; 555 } 556 557 @property title() inout 558 { 559 return _title; 560 } 561 562 @property title(inout string newTitle) 563 { 564 if (_glfwWindow) 565 { 566 glfwSetWindowTitle(_glfwWindow, toStringz(newTitle)); 567 } 568 } 569 570 @property visible() inout 571 { 572 if (_glfwWindow is null) 573 return false; 574 return glfwGetWindowAttrib(cast(GLFWwindow*)_glfwWindow, GLFW_VISIBLE) == 1; 575 } 576 577 @property position() const 578 { 579 int x; 580 int y; 581 glfwGetWindowPos(cast(GLFWwindow*)_glfwWindow, &x, &y); 582 return [x, y]; 583 } 584 585 @property size() const 586 { 587 int w, h; 588 glfwGetWindowSize(cast(GLFWwindow*)_glfwWindow, &w, &h); 589 return [w, h]; 590 } 591 592 593 /// Get a copy of image currently drawn on figure's canvas. 594 @property image() const 595 in 596 { 597 assert(width && height); 598 } 599 body 600 { 601 Image im = new Image(width, height, ImageFormat.IF_RGB, BitDepth.BD_8); 602 im.data[] = _data[]; 603 return im; 604 } 605 606 /// Show the figure window. 607 void show() 608 { 609 if (_glfwWindow) 610 glfwShowWindow(_glfwWindow); 611 } 612 613 /// Show the figure window. 614 void hide() 615 { 616 if (_glfwWindow) 617 glfwHideWindow(_glfwWindow); 618 } 619 620 /// Clear canvas content of this figure. 621 void clear() 622 { 623 _data[] = cast(ubyte)0; 624 } 625 626 /// Move figure window to given position on screen. 627 void moveTo(int x, int y) 628 { 629 glfwSetWindowPos(_glfwWindow, x, y); 630 } 631 632 /// ditto 633 void moveTo(int[] pos) 634 { 635 assert(pos.length == 2); 636 move(pos[0], pos[1]); 637 } 638 639 /// Offset figure window position by given values. 640 void move(int x, int y) 641 { 642 auto p = this.position; 643 glfwSetWindowPos(_glfwWindow, p[0] + x, p[1] + y); 644 } 645 646 /// ditto 647 void move(int[] offset) 648 { 649 move(offset[0], offset[1]); 650 } 651 652 /// Draw image onto figure canvas. 653 void draw(Image image) 654 { 655 Image showImage = adoptImage(image); 656 657 if (_width != showImage.width || _height != showImage.height) 658 { 659 _width = cast(int)showImage.width; 660 _height = cast(int)showImage.height; 661 _data = showImage.data.dup; 662 } 663 else 664 { 665 assert(_data.length == showImage.data.length); 666 _data[] = showImage.data[]; 667 } 668 669 fitWindow(); 670 } 671 672 /// Draw slice of image onto figure canvas. 673 void draw(SliceKind kind, size_t[] packs, Iterator) 674 (Slice!(kind, packs, Iterator) image, ImageFormat format = ImageFormat.IF_UNASSIGNED) 675 { 676 import std.range.primitives : ElementType; 677 import mir.ndslice.topology : as; 678 import mir.ndslice.allocation : slice; 679 680 static assert(packs.length == 1, "Cannot draw packed slices."); 681 682 alias T = ElementType!Iterator; 683 684 Slice!(SliceKind.contiguous, packs, ubyte*) showImage; 685 static if ( is(T == ubyte) ) 686 showImage = image.assumeContiguous; // TODO: test if its contiguous 687 else 688 showImage = image.as!ubyte.slice; 689 690 if (format == ImageFormat.IF_UNASSIGNED) 691 draw( showImage.asImage() ); 692 else 693 draw(showImage.asImage(format) ); 694 } 695 696 /** 697 Draw the GGPlotD context on this figure's canvas. 698 699 Important Notes: 700 - ggplotd's coordinate system starts from down-left corner. To match 701 the image coordinate system (which starts from up-left corner), y axis 702 is flipped in the given plot. 703 - GGPlotD's margins are zeroed out in this function, and axes hidden. 704 - GGPlotD's axes ranges are configured in this function to match figure size (width and height). 705 */ 706 version(ggplotd) void draw(GGPlotD plot) 707 { 708 drawGGPlotD(plot, _data, _width, _height); 709 fitWindow(); 710 } 711 712 private void fitWindow() 713 { 714 glfwSetWindowSize(_glfwWindow, _width, _height); 715 } 716 717 private void render() 718 { 719 glfwMakeContextCurrent(_glfwWindow); 720 721 int fBufWidth, fBufHeight; 722 glfwGetFramebufferSize(_glfwWindow, &fBufWidth, &fBufHeight); 723 724 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 725 glDisable(GL_DEPTH_TEST); 726 727 glViewport(0, 0, fBufWidth, fBufHeight); 728 729 glMatrixMode(GL_PROJECTION); 730 glLoadIdentity(); 731 732 glOrtho(0, width, 0, height, 0.1, 1); 733 734 glPixelZoom(fBufWidth / width, -fBufHeight / height); 735 736 glRasterPos3f(0, height - 1, -0.3); 737 738 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 739 glDrawPixels(_width, _height, GL_RGB, GL_UNSIGNED_BYTE, _data.ptr); 740 741 glFlush(); 742 glEnable(GL_DEPTH_TEST); 743 744 glfwSwapBuffers(_glfwWindow); 745 } 746 747 private void setupWindow() 748 { 749 glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); 750 _glfwWindow = glfwCreateWindow(_width, _height, toStringz(_title), null, null); 751 if (!_glfwWindow) 752 { 753 throw new Exception("Cannot create window of size " ~ [_width, height].to!string); 754 } 755 glfwMakeContextCurrent(_glfwWindow); 756 } 757 758 private void defaultCloseCallback(Figure figure) 759 { 760 glfwHideWindow(figure._glfwWindow); 761 } 762 } 763 764 // Constants //////////////////////////// 765 766 immutable KEY_UNKNOWN = -1; 767 immutable KEY_SPACE = 32; 768 immutable KEY_APOSTROPHE = 39; /* ' */ 769 immutable KEY_COMMA = 44; /* , */ 770 immutable KEY_MINUS = 45; /* - */ 771 immutable KEY_PERIOD = 46; /* . */ 772 immutable KEY_SLASH = 47; /* / */ 773 immutable KEY_SEMICOLON = 59; /* ; */ 774 immutable KEY_EQUAL = 61; /* = */ 775 immutable KEY_LEFT_BRACKET = 91; /* [ */ 776 immutable KEY_BACKSLASH = 92; /* \ */ 777 immutable KEY_RIGHT_BRACKET = 93; /* ] */ 778 immutable KEY_GRAVE_ACCENT = 96; /* ` */ 779 immutable KEY_ESCAPE = 256; 780 immutable KEY_ENTER = 257; 781 immutable KEY_TAB = 258; 782 immutable KEY_BACKSPACE = 259; 783 immutable KEY_INSERT = 260; 784 immutable KEY_DELETE = 261; 785 immutable KEY_RIGHT = 262; 786 immutable KEY_LEFT = 263; 787 immutable KEY_DOWN = 264; 788 immutable KEY_UP = 265; 789 immutable KEY_PAGE_UP = 266; 790 immutable KEY_PAGE_DOWN = 267; 791 immutable KEY_HOME = 268; 792 immutable KEY_END = 269; 793 immutable KEY_CAPS_LOCK = 280; 794 immutable KEY_SCROLL_LOCK = 281; 795 immutable KEY_NUM_LOCK = 282; 796 immutable KEY_PRINT_SCREEN = 283; 797 immutable KEY_PAUSE = 284; 798 immutable KEY_F1 = 290; 799 immutable KEY_F2 = 291; 800 immutable KEY_F3 = 292; 801 immutable KEY_F4 = 293; 802 immutable KEY_F5 = 294; 803 immutable KEY_F6 = 295; 804 immutable KEY_F7 = 296; 805 immutable KEY_F8 = 297; 806 immutable KEY_F9 = 298; 807 immutable KEY_F10 = 299; 808 immutable KEY_F11 = 300; 809 immutable KEY_F12 = 301; 810 immutable KEY_F13 = 302; 811 immutable KEY_F14 = 303; 812 immutable KEY_F15 = 304; 813 immutable KEY_F16 = 305; 814 immutable KEY_F17 = 306; 815 immutable KEY_F18 = 307; 816 immutable KEY_F19 = 308; 817 immutable KEY_F20 = 309; 818 immutable KEY_F21 = 310; 819 immutable KEY_F22 = 311; 820 immutable KEY_F23 = 312; 821 immutable KEY_F24 = 313; 822 immutable KEY_F25 = 314; 823 immutable KEY_KP_0 = 320; 824 immutable KEY_KP_1 = 321; 825 immutable KEY_KP_2 = 322; 826 immutable KEY_KP_3 = 323; 827 immutable KEY_KP_4 = 324; 828 immutable KEY_KP_5 = 325; 829 immutable KEY_KP_6 = 326; 830 immutable KEY_KP_7 = 327; 831 immutable KEY_KP_8 = 328; 832 immutable KEY_KP_9 = 329; 833 immutable KEY_KP_DECIMAL = 330; 834 immutable KEY_KP_DIVIDE = 331; 835 immutable KEY_KP_MULTIPLY = 332; 836 immutable KEY_KP_SUBTRACT = 333; 837 immutable KEY_KP_ADD = 334; 838 immutable KEY_KP_ENTER = 335; 839 immutable KEY_KP_EQUAL = 336; 840 immutable KEY_LEFT_SHIFT = 340; 841 immutable KEY_LEFT_CONTROL = 341; 842 immutable KEY_LEFT_ALT = 342; 843 immutable KEY_LEFT_SUPER = 343; 844 immutable KEY_RIGHT_SHIFT = 344; 845 immutable KEY_RIGHT_CONTROL = 345; 846 immutable KEY_RIGHT_ALT = 346; 847 immutable KEY_RIGHT_SUPER = 347; 848 immutable KEY_MENU = 348; 849 immutable KEY_LAST = KEY_MENU; 850 851 immutable MOD_SHIFT = 0x0001; 852 immutable MOD_CONTROL = 0x0002; 853 immutable MOD_ALT = 0x0004; 854 immutable MOD_SUPER = 0x0008; 855 856 immutable MOUSE_BUTTON_1 = 0; 857 immutable MOUSE_BUTTON_2 = 1; 858 immutable MOUSE_BUTTON_3 = 2; 859 immutable MOUSE_BUTTON_4 = 3; 860 861 private: 862 863 static int GLFW_STATUS; 864 865 // Checks if drawing context has been initialized. 866 enum checkContextInit = q{ 867 if (GLFW_STATUS == GLFW_FALSE) { 868 throw new ContextNotInitialized(); 869 } 870 }; 871 872 // initialize glfw and global callbacks 873 static this() 874 { 875 import std.stdio; 876 877 GLFW_STATUS = glfwInit(); 878 879 setCharCallback((uint key) { _lastKey = key; }); 880 881 setKeyPressCallback((int key, int scancode, int action, int mods) { 882 /* 883 char callback takes priority with character keyboard inputs, 884 so only override the _lastKey value if its -1, which means there 885 was no char callback previously. 886 */ 887 if (_lastKey == -1) 888 _lastKey = key; 889 }); 890 } 891 892 private: 893 894 Figure[] _figures; // book-keeping of each running figure. 895 int _lastKey = -1; // last hit key 896 897 KeyPressCallback _keyPressCallback; // global key press callback 898 CharCallback _charCallback; // global char callback 899 900 void keyCallbackWrapper(int mods, int action, int scancode, int key, GLFWwindow* window) 901 { 902 if (_keyPressCallback) 903 _keyPressCallback(key, scancode, action, mods); 904 } 905 906 void charCallbackWrapper(uint key, GLFWwindow* window) 907 { 908 if (_charCallback) 909 _charCallback(key); 910 } 911 912 void cursorCallbackWrapper(double y, double x, GLFWwindow* window) 913 { 914 foreach (f; _figures) 915 { 916 if (f._glfwWindow == window) 917 { 918 if (f._cursorCallback) 919 f._cursorCallback(f, x, y); 920 break; 921 } 922 } 923 } 924 925 void mouseCallbackWrapper(int mods, int action, int button, GLFWwindow* window) 926 { 927 foreach (f; _figures) 928 { 929 if (f._glfwWindow == window) 930 { 931 if (f._mouseCallback) 932 f._mouseCallback(f, button, action, mods); 933 break; 934 } 935 } 936 } 937 938 void closeCallbackWrapper(GLFWwindow* window) 939 { 940 foreach (f; _figures) 941 { 942 if (f._glfwWindow == window) 943 { 944 if (f._closeCallback) 945 f._closeCallback(f); 946 break; 947 } 948 } 949 } 950 951 Image adoptImage(Image image) 952 { 953 import dcv.imgproc.color : yuv2rgb, gray2rgb; 954 955 Image showImage = (image.depth != BitDepth.BD_8) ? image.asType!ubyte : image; 956 import mir.ndslice.topology; 957 switch (showImage.format) 958 { 959 case ImageFormat.IF_RGB_ALPHA: 960 showImage = showImage.sliced[0 .. $, 0 .. $, 0 .. 2].asImage(ImageFormat.IF_RGB); 961 break; 962 case ImageFormat.IF_BGR: 963 foreach (e; showImage.sliced.pack!1.flattened) 964 { 965 auto t = e[0]; 966 e[0] = e[2]; 967 e[2] = t; 968 } 969 break; 970 case ImageFormat.IF_BGR_ALPHA: 971 foreach (e; showImage.sliced.pack!1.flattened) 972 { 973 auto t = e[0]; 974 e[0] = e[2]; 975 e[2] = t; 976 } 977 showImage = showImage.sliced[0 .. $, 0 .. $, 0 .. 2].asImage(ImageFormat.IF_RGB); 978 break; 979 case ImageFormat.IF_YUV: 980 showImage = showImage.sliced.yuv2rgb!ubyte.asImage(ImageFormat.IF_RGB); 981 break; 982 case ImageFormat.IF_MONO: 983 showImage = showImage.sliced.flattened.sliced(image.height, image.width) 984 .gray2rgb!ubyte.asImage(ImageFormat.IF_RGB); 985 break; 986 default: 987 break; 988 } 989 return showImage; 990 } 991 992 version(ggplotd) void drawGGPlotD(GGPlotD gg, ubyte[] data, int width, int height) 993 { 994 import std.range : iota; 995 import cairo = cairo; 996 997 gg.put(xaxisRange(0, width)).put(yaxisRange(0, height)); // fit range to image size. 998 gg.put(xaxisOffset(-10)).put(yaxisOffset(-10)); // change offset to hide axes. 999 gg.put(Margins(0, 0, 0, 0)); // Change margins, to match image coordinates. 1000 1001 cairo.Surface surface = new cairo.ImageSurface(cairo.Format.CAIRO_FORMAT_RGB24, width, height); 1002 gg.drawToSurface(surface, width, height); 1003 1004 surface.flush(); 1005 1006 auto imSurface = cast(cairo.ImageSurface)surface; 1007 auto surfData = imSurface.getData(); 1008 1009 foreach (r; iota(height)) 1010 foreach (c; 0 .. width) 1011 { 1012 auto pixpos = (height - r - 1) * width * 4 + c * 4; 1013 auto dpixpos = r * width * 3 + c * 3; 1014 auto alpha = surfData[pixpos + 3]; 1015 if (alpha) 1016 { 1017 auto af = cast(float)alpha / 255.0f; 1018 data[dpixpos + 0] = cast(ubyte)(data[dpixpos + 0] * (1.0f - af) + surfData[pixpos + 2] * af); 1019 data[dpixpos + 1] = cast(ubyte)(data[dpixpos + 1] * (1.0f - af) + surfData[pixpos + 1] * af); 1020 data[dpixpos + 2] = cast(ubyte)(data[dpixpos + 2] * (1.0f - af) + surfData[pixpos + 0] * af); 1021 } 1022 } 1023 }