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 }