Creating a graphics window to show a TV static demo

My simpledisplay.d file is a module to create and interact with a simple window, enabling easy graphics display. On Windows, it wraps the Win32 GDI API, and on other systems it uses Xlib for broad compatibility with minimal dependencies.

Getting ready

Download simpledisplay.d and its dependency color.d from my Github.

How to do it…

Let's execute the following steps to create a graphics window:

  1. Import simpledisplay.
  2. Create an image to use as your pixel drawing buffer.
  3. Create a SimpleWindow object with the image's size.
  4. Create an image to use as your buffer.
  5. Create and run an event loop with window.eventLoop.
  6. Pass 25 as the first argument to eventLoop to request a 25 millisecond timer.
  7. Pass a delegate that takes no arguments to serve as the timer handler. In this function, fill the image with random white and black pixels, get a painter with window.draw, and draw the image to it with painter.drawImage.
  8. Pass a delegate that takes a KeyEvent argument to serve as a key handler. Call window.close to close the window and exit the loop when you receive the key to exit.
  9. Implement the pausing functionality by making the timer delegate a return call if paused and changing the pause flag in the key handler.
  10. Compile the program with dmd yourfile.d simpledisplay.d color.d.

The code is as follows:

import simpledisplay;

void main() {
  bool paused;
  auto image = new Image(256, 256);
  auto window = new SimpleWindow(image.width, image.height);
  window.eventLoop(25,
    () {
      if(paused) return;
      import std.random;
      foreach(y; 0 .. image.height)
      foreach(x; 0 .. image.width)
        image.putPixel(x, y, uniform(0, 2) ? Color.black : Color.white);
      auto painter = window.draw();
      painter.drawImage(Point(0, 0), image);
    },
    (KeyEvent ke) {
      if(ke.key == Key.Space)
        paused = !paused;
      if(ke.key == Key.Escape)
        window.close();
    }
  );
}

Running the program will pop up a window with random white noise, similar to static on an old TV set, as shown in the following screenshot:

How to do it…

As you hold the Space bar, the animation will temporarily stop, and releasing the Space bar will cause it to resume.

How it works…

The simpledisplay.d module includes the necessary operating system function bindings and some cross-platform wrapping to enable the easy creation of a graphics window with no outside dependencies except color.d. It doesn't even use Phobos. You may notice improved compile times because neither file imports the standard library.

The implementation uses mixin templates to provide the different code for each platform. If I write this again, I will not use this approach—it resulted in code duplication and difficulty in maintenance. However, this approach does have one potential saving grace: it can be used to separate the operating system implementations into entirely different modules, which may become more maintainable. I didn't do this because I wanted the file to just work when downloaded individually. The color.d module eventually became a requirement—without it, the image modules and windowing module will both need to declare separate structures to hold a color, leading to silly incompatibilities between them. Even if two structures in different modules have exactly the same layout, they are considered separate, incompatible types (unless you use an adapter or a cast). However, while the ideal of one standalone file did not work, I still wanted to stay as close to it as possible and opted for one large simpledisplay.d file that covers all platforms.

Another challenge of the implementation was mapping window procedures of the operating system level back to the SimpleWindow object of D's class. The window procedures must match a specific signature and calling convention to be callable by the operating system (or, in the case of X11, the event loop must work with window IDs from the X server, which similarly leaves no room for the injection of a D class).

To solve this, I used an associative array of window handle/ID to window class mappings. The constructor of SimpleWindow creates the window and stores the native window handle in a static associative array that leads back to itself. Similarly, the destructor of SimpleWindow removes itself from this map. When a window handle comes through the event loop, the associative array is used to get the class instance and forward the message to it to be processed.

The event loop of SimpleWindow uses an array of delegates that are matched on argument types, inspired by std.concurrency. This is implemented with a foreach loop and __traits(compiles); it loops over its arguments and attempts to assign them to the event handling delegates. If it succeeds, it accepts the handler. If not, a static assert is used to issue a compile-time error.

Finally, simpledisplay.d also supports the following two forms of drawing:

  • Drawing directly to a window or image using the operating system functions
  • Writing straight to a local image's pixels

To draw to the window, a draw method that returns a Painter struct is used. The painter retains a reference to the window for use with native API functions and has a destructor to automatically swap buffers and clean up resources when it goes out of scope.

Do not use draw before entering your event loop unless you surround the code in braces to ensure the painter goes out of scope before entering the loop. Otherwise, its destructor will not fire until program termination!

To draw directly to an image, the implementation tries to achieve efficiency by using a final class and shared memory on X11, when available. The final class provides a ~10 percent speed boost over a regular class because it contains no virtual functions, which can be a performance bottleneck on modern CPUs. The shared memory obviates image copying to and from the display server on the network-transparent X protocol, saving a significant amount of time. All this is done transparently to the user.

An interesting consequence of the shared memory approach, however, is the requirement to ensure cleanup tasks are always performed. As the resource is shared across processes, the operating system will not automatically release the shared memory handles when your program terminates. Thus, a signal handler must be installed to handle SIGINT when the user presses Ctrl + C. This is the same situation that was discussed in the Getting real-time input from the terminal recipe. If you fail to do this, it may make your X server difficult to use because the number of possible shared memory handles is limited.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.141.115