Beginning multimedia with arsd


In this tutorial I will do my best to teach you how to use the arsd library the easiest and fastest way possible. Once you finish this tutorial you may move on to the library's documentation and learn the rest on your own. In this tutorial I will show you how to create a window, display images and set the window to receive input from the mouse and keyboard.

Keep in mind that I will write starting from the premise that you already know a good amount of programming and also that you know well the syntax of D. This tutorial is for multimedia beginners, not D beginners and certainly not programming beginners.

Here we go. First you need to download the arsd library into your project's folder. This library is awesome because it does not require a package manager, you can use it as if it were nothing but a module, just import it into your source code, nothing more. And it is very rich in features, it contains tools for multimedia, games, web, desktop, database and others. I use it for everything. It is still being developed so it still has some flaws and lacks some features, but it's already good enough for you to do so much. It was created by an American developer named Adam Ruppe.

It can be downloaded from the link https://drive.google.com/file/d/1Nz59Oqh4nymdUWsljo999FuGysaViMj9/view?usp=sharing and you can read its documentation on http://dpldocs.info/experimental-docs/arsd.html.

Now that you have downloaded it you just need to import the necessary modules, arsd.simpledisplay contains tools to manipulate and display images and create windows, arsd.simpleaudio contains tools to play audio, and these are only 2 of the many modules arsd has. The focus of this tutorial will be arsd.simpledisplay because it is the main module for GUI and image display.

Now we will learn how to simply display an image on the screen. First download a picture of a cat (I love cats) from Google into the project's folder and then read the following code.

import arsd.image : loadImageFromFile;
import arsd.simpledisplay : Image, MemoryImage, displayImage;

void main()
{
    MemoryImage catMemImg = loadImageFromFile("cat.jpeg");
    Image catImg = Image.fromMemoryImage(catMemImg);
    displayImage(catImg);
}

Don't just copy-paste, type it yourself instead (in order to practice) on your own editor (I'm using Bluefish). Remember to compile with dmd using the -m64, the -i and the -J. flags.

Now let's analyze each line of this code. Line number 1 simply imports stuff from the arsd.image module. Line number 2 imports stuff from the arsd.simpledisplay module. Line number 6 creates a variable which is an image in memory, so now we have the image loaded in memory but it is not displayable yet (notice that in your environment it may be .jpg or .JPG rather than .jpeg). Line number 7 creates the variable which is an image that we can display on the screen, so now we are all set. Finally line number 8 just displays the image on the screen and waits for you to close it. See how easy it was?

Now let's learn what each thing in that code is. arsd.image is a module with tools to load images and arsd.simpledisplay was already explained above. 'MemoryImage' is an 'interface' which represents an image loaded in memory which is not yet displayable. 'loadImageFromFile()' is a function that returns a 'MemoryImage' variable out of an image file whose name is passed as a string argument (the file must be in the project's folder and it accepts .jpg, .png, .bmp and maybe other formats). 'Image' is a 'class' which represents an image that can already be displayed on the screen. 'Image.fromMemoryImage()' is just a member function that returns an 'Image' object out of a 'MemoryImage' object and finally 'displayImage()' is a function that displays the image on a window.

Now we will learn how to create a window. Take a look at this code:

import arsd.simpledisplay : SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow();


    window.eventLoop(0);
}

Line number 5 creates the window which automatically pops up when you run the program and line number 6 finally starts the window's event loop so it waits for input and responds accordingly, although in this case there are no event handlers.

Now let's learn what each thing in this code is. 'SimpleWindow' is a 'class' that represents a window, it will be the main feature of the arsd library when developing multimedia programs and it is also the flagship of the arsd.simpledisplay module, '.eventLoop()' is the most important member function of the 'SimpleWindow' 'class' because it starts the event loop, the number passed as argument (0 in this case) says how many milliseconds each loop iteration will last, at the end of each loop iteration the GUI is flushed, meaning all changes are finally displayed (if you draw a circle on the screen it will be visible once the GUI is flushed), however since I've set the time window to 0 it will not run any loop since it doesn't have time to do so, therefore it will just be an immutable window in this case. The loop ends when you close the window by clicking the Close button. There is much more to this member function, we will learn one feature at a time.

Now we will learn how to use the 'SimpleWindow' 'class' properly and some of its main features. First we should learn that you can set the properties of the 'SimpleWindow' object with arguments. You can pass the width, the height and even the title as arguments. Take a look at this code:

import arsd.simpledisplay : SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(300, 200, "tiny window");


    window.eventLoop(0);
}

In this code we create a 300 pixels wide and 200 pixels high window with the title "tiny window".

You can get information about the window by calling some of its member functions such as '.width()', '.height()' and '.title()'. Take a look at the following code:

import arsd.simpledisplay : SimpleWindow;
import std.stdio : writeln;

void main()
{
    SimpleWindow window = new SimpleWindow(300, 200, "tiny window");


    window.eventLoop(0);


    writeln(window.width(), ' ', window.height(), ' ', window.title());
}

Here we create the window and then write on the command prompt its width, height and title separated by spaces using the 'writeln()' function.

Now let's learn some fun methods that 'SimpleWindow' has. Make sure your speakers are on and take a look at this code:

import arsd.simpledisplay : SimpleWindow;
import std.stdio : writeln;

void main()
{
    SimpleWindow window = new SimpleWindow(675, 200, "not so tiny window");


    if (window.width() < 500)
        window.close();
    else
    {
        window.beep();
        window.eventLoop(0);
        window.beep();
    }
}

Here we create a similar window on line 6 but this time with a width of 675 pixels and then on line 7 there is an 'if' statement which closes the window if the width is less then 500 and then there is the 'else' statement on line 9 which makes the window beep before and after the event loop. Try this exact same code again but with a width smaller than 500 and see what happens. In the former it plays a beep right after the window pops up and another beep right after you close it. In the latter it closes the window right after it pops up with no beeps.

Now let's learn what each thing in this code is. First we see the '.close()' member function being called on line 8, this member function closes the window, then we see the '.beep()' member function being called on lines 11 and 13, this member function plays a beep (on Windows, I don't know about other OSs).

Now let's learn how to draw random images anywhere on the screen at any given time. First you need to download an image, I downloaded a picture of a super cute koala. For the sake of this example, make sure the image is no bigger than 300 pixels wide or high, you may edit and resize it. Then take a look at the following code:

import arsd.image : loadImageFromFile;
import arsd.simpledisplay : Image, MemoryImage, Point, ScreenPainter, SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(600, 600, "Drawings");
    MemoryImage koalaMemImg = loadImageFromFile("koala.jpeg");
    Image koalaImg = Image.fromMemoryImage(koalaMemImg);
    int x = 50, y = 50;
    Point p;


    window.eventLoop(1000,
    {
        p = Point(x, y);
        ScreenPainter painter = window.draw();
        painter.drawImage(p, koalaImg);
        x += 100, y += 100;


        if (x > 600)
            window.close();
    });
}

Here we create a window on line 6, a memory image on line 7 and a displayable image on line 8. Then we create 2 'int' variables which will represent the coordinates on line 9. Then we create a 'Point' variable called 'p' on line 10. Then we start the event loop on line 11 and then we set the coordinates of the point 'p' on line 13. Then we create a 'ScreenPainter' variable called 'painter' on line 14 and then we use it to draw the image on the point 'p' on line 15. Then we just change the values of 'x' and 'y' on line 16 and then on line 17 we have an 'if' statement that closes the window when the image goes beyond the border.

Now let's learn what each thing in this code is. First we have the new data type 'Point', this is a 'struct' that represents a point on the window, the coordinates are passed as arguments. Then we have the data type 'ScreenPainter' which is a 'struct' that draws stuff on the window, this is what you will use to draw anything on the window and it is returned by the member function '.draw()' from 'SimpleWindow'. Then we have the function '.drawImage()' from 'ScreenPainter' which draws an 'Image' object with its upper left vertex on a point represented by a 'Point' variable. Keep in mind that when working with windows on the computer screen the origin (0, 0) is the upper left corner of the window and the x coordinates grow towards the right while the y coordinates grow towards the bottom (yeah, it is not like a cartesian plane). The rest of the code is just stuff you already know and by analyzing the code above you can figure out how to use it all. Oh, I almost forgot, instead of the number 0 I typed 1000 as the first argument of the event loop, that is because I want this program to wait 1 second (1000 milliseconds) before drawing the image again.

Now let's learn how to draw some interesting figures on our window. This time you don't need to download anything, arsd comes equipped with functions for you to draw polygons, curves, lines and even words on the window.

Take a look at this code:

import arsd.simpledisplay : Color, Point, ScreenPainter, SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(600, 600, "Artworks");


    window.eventLoop(1,
    {
        ScreenPainter painter = window.draw();
        painter.outlineColor = Color.green();
        painter.drawLine(Point(10, 10), Point(500, 500));
    });
}

On line 5 we create the window we will use as a canvas for our artistic skills (if we have any). On line 6 we start the event loop and then on line 8 we create the screen painter and then on line 9 we set the painter's outline color to green and then on line 10 we draw a line connecting point (10, 10) to point (500, 500).

Now let's learn the new features I just used. '.outlineColor' is a variable of the 'ScreenPainter' 'struct' which is the main color it uses when drawing (or writing) stuff on the window and it will be the outline color of closed figures (e.g. a rectangle). 'Color' is an 'enum' which contains several different colors for you to use, such as white, yellow, red, blue and even transparent, all of which are static functions. And if you are wondering why I wrote 1 as the time window for our loop, it's because if I had written 0 then it would have to draw in 0 milliseconds which is impossible since it needs time to execute the instructions (your computer has its limitations). Try it with 0 and see what happens.

Now we will let our inner creative artist take over our keyboard to create real art. Trust me, we all have that inside us.

Take a look at this code:

import arsd.simpledisplay : Color, Point, ScreenPainter, SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(600, 600, "Artworks");
    int x = 0, y = 600;


    void updatePoints()
    {
        if (x < 600 && y == 600)
            x++;
        else if (x == 600 && y > 0)
            y--;
        else if (x > 0 && y == 0)
            x--;
        else if (x == 0 && y < 600)
            y++;
    }


    {window.draw().clear(Color.black());}


    window.eventLoop(1,
    {
        ScreenPainter painter = window.draw();
        painter.outlineColor = Color.white();
        painter.drawLine(Point(300, 300), Point(x, y));
        updatePoints();
    });
}

Run this code and watch what happens. This is very similar to the code we used before only that I added the function 'updatePoints()' in order to properly update the x and y coordinates and then I changed the start and end points of our line (this time it was red) so it starts at the very center of the window and ends at the very edge.

Now let's learn how to draw geometric shapes and more. Take a look at this code:

import arsd.simpledisplay : Color, Point, ScreenPainter, SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(600, 600, "Geometric shapes and more");
    int counter;


    window.eventLoop(2000,
    {
        ScreenPainter painter = window.draw();
        painter.clear(Color.black());
        painter.outlineColor = Color.green();
        painter.fillColor = Color.blue();


        switch(counter)
        {
            case 0:
                painter.drawRectangle(Point(10, 10), 50, 30);
                break;
            case 1:
                painter.drawArc(Point(60, 60), 40, 80, 0, 90 * 64);
                break;
            case 2:
                painter.drawEllipse(Point(120, 140), Point(200, 180));
                break;
            case 3:
                painter.drawCircle(Point(200, 200), 40);
                break;
            case 4:
                painter.drawPixel(Point(250, 250));
                break;
            case 5:
                painter.drawText(Point(260, 260), "D is an awesome language!");
                break;
            default:
                window.close();
        }


        counter++;
    });
}

On line 5 we create the window. On line 6 we create a counter variable. On line 7 we start the event loop. On line 10 we make the window black (so we can see the images we will draw). On lines 11 and 12 we set the colors of the screen painter. The next lines are just a 'switch' statement which draws something different for each value of the counter. On line 34 it ends the program by closing the window. On line 36 it increments the counter.

Now let's learn what each thing in this code is. First notice that I wrote 2000 for the loop's time window because I want an interval of 2 seconds between each drawing. Then notice that I created a counter variable that is incremented at the end of the loop in order to control the order of what is being drawn. Then on line 10 I clear the window using the '.clear(Color.black())' member function which just turns everything black, or another color (from the 'Color' 'enum') you may pass as argument. On line 16 I draw a rectangle using the '.drawRectangle()' member function which requires the upper left vertex, the width and height as arguments. On line 19 I draw an arc using the '.drawArc()' member function which requires as arguments the upper left point, the width, the height, the start and the end angles (these must be multiplied by 64). On line 22 I draw an ellipse using the '.drawEllipse()' member function which requires the upper left and lower right vertices of the bounding rectangle as arguments. On line 25 I draw a circle using the '.drawCircle()' member function which requires the upper left point and the radius as arguments. On line 28 I draw a single pixel using the 'drawPixel()' member function which requires only the point of the pixel as argument. On line 31 I draw some text using the 'drawText()' member function which requires the upper left point and the string as arguments. You have probably noticed that you always pass the upper left point to tell the functions where to draw, it is common practice in programming to use the upper left point as reference.

Now you already know how to create a window and draw all sorts of stuff on it, but let's suppose you want to create a game, then you will need to be drawing the characters and interactive objects (sprites) over and over, you could do that by creating an 'Image' object and using the '.drawImage()' member function of the 'ScreenPainter' 'struct' but that would be the wrong way, do that only when you want to draw an image once or twice, the right way would be to create a sprite object and use a function designed just for sprites. I will now show you how to do that. Let's create a program that displays a moving bison. Download from google a small picture of a bison (not the one from Street Fighter). Then take a look at this code:

import arsd.image : loadImageFromFile;
import arsd.simpledisplay : Image, MemoryImage, Point, ScreenPainter, SimpleWindow, Sprite;

void main()
{
    SimpleWindow window = new SimpleWindow;
    MemoryImage memImg = loadImageFromFile("bison.jpeg");
    Image img = Image.fromMemoryImage(memImg);
    Sprite sprite = new Sprite(window, img);
    int x, y;


    window.eventLoop(1000,
    {
        ScreenPainter painter = window.draw();
        sprite.drawAt(painter, Point(x, y));
        x += 10, y += 10;
    });
}

This is a program just like all the others we wrote, but here I added something new, on lines 6, 7 and 8 I create a window and an image object to be displayed, then on line 9 I create a sprite object using the 'Sprite' 'class' and then on line 10 I create the variables 'x' and 'y' to keep track of the coordinates. Then we start the event loop and on line 14 we draw the sprite using its own member function '.drawAt()' and then we increment the 'x' and 'y' variables on line 15 so the sprite moves, animals look more beautiful when they are moving.

Now let's repeat the sentence I always use and you must be getting tired of. NOW LET'S LEARN WHAT EACH THING IN THIS CODE IS: first the 'Sprite' called on line 9 is a 'class' which represents a sprite, this is what you must use when working with games and similar stuff that draw images repeatedly. You call it with the window it will be used on and the image it will represent as arguments. Then whenever you want to draw the sprite on the window you need to call the member function '.drawAt()' and pass it the 'ScreenPainter' (painter) that will draw the sprite and the 'Point' (coordinates) where it must be drawn (upper left vertex) as arguments.

Now we will learn how to receive mouse input, meaning how to allow the user to click on the window and make things happen. Take a look at this code:

import arsd.simpledisplay : MouseButton, MouseEvent, MouseEventType, SimpleWindow;
import std.stdio : writefln, writeln;

void main()
{
    SimpleWindow window = new SimpleWindow(500, 500, "Mouse input");


    window.eventLoop(0,
    delegate (MouseEvent event)
    {
        if (event.type == MouseEventType.buttonPressed)
        {
            if (event.button == MouseButton.left)
                writeln("left click");
            else if (event.button == MouseButton.right)
                writeln("right click");
            else if (event.button == MouseButton.middle)
                writeln("middle click");
        }
        else if (event.type == MouseEventType.buttonReleased)
        {
            if (event.button == MouseButton.left)
                writeln("releasing left");
            else if (event.button == MouseButton.right)
                writeln("releasing right");
            else if (event.button == MouseButton.middle)
                writeln("releasing middle");
        }
        else if (event.type == MouseEventType.motion)
            writefln("coordinates: (%d, %d)", event.x, event.y);
    });
}

Alright, now let's analyze this code. We just create a window and its event loop. Then on line 8 we create the code block designed for mouse events and then we use a bunch of if-else to tell it what to do according to the mouse actions we perform. Try moving the arrow, clicking with all buttons, then click and drag to see what happens. It writes on the command prompt (terminal) exactly which button you are clicking or releasing and it also writes the exact coordinates of the arrow inside the window.

Now let's learn what each thing in this code is. On line 8 you have '(MouseEvent event)' which is just a way to tell the event loop that this will be the code block to deal with mouse events. 'MouseEvent' is a 'struct' that represents a mouse event. I have named the 'MouseEvent' variable as 'event' to make it intuitive. Then on line 10 you will see 'event.type == MouseEventType.buttonPressed', in this case '.type' is the variable that tells which type of event it is and then we have 'MouseEventType' which is an 'enum' with the 3 possible mouse event types ('motion/buttonPressed/buttonReleased'). Then on line 12 we have 'event.button == MouseButton.left', in this case '.button' is the variable that tells which mouse button you're using and 'MouseButton' is an 'enum' with all the possible mouse buttons (visit http://dpldocs.info/experimental-docs/arsd.simpledisplay.MouseButton.html for the complete list). Lastly, on line 29 we have 'event.x, event.y', in this case '.x' and '.y' are the variables that tell the coordinates of the arrow tip inside the window. So basically in the outer if-else we check which type of event it is and in the nested one we check which button is being used. Notice that I set the time window to 0, that is because when an event happens the loop stops to register the event, so it does not need a time interval. Also notice that I added the word 'delegate' in front of '(MouseEvent event)', that is because otherwise the compiler would raise an error because we would have a delegate vs function issue, in the previous codes we have been calling the screen painter with 'window.draw();' which references an outside variable and automatically turns it into a delegate. Since we don't do it in this code we need to enforce it with that keyword. Now read the code again and play with the program in order to see how it works.

Using the mouse is fun, but real world programs will require the keyboard as well. Coding keyboard input is not very different from coding the mouse input above. Here it is:

import arsd.simpledisplay : Key, KeyEvent, SimpleWindow;
import std.stdio : writeln;

void main()
{
    SimpleWindow window = new SimpleWindow(500, 500, "Keyboard input");


    window.eventLoop(0,
    delegate (KeyEvent event)
    {
        if (event.pressed)
        {
            if (event.key == Key.A)
                writeln("pressed A");
            else if (event.key == Key.B)
                writeln("pressed B");
            else if (event.key == Key.C)
                writeln("pressed C");
        }
        else
        {
            if (event.key == Key.A)
                writeln("released A");
            else if (event.key == Key.B)
                writeln("released B");
            else if (event.key == Key.C)
                writeln("released C");
        }
    });
}

Here we just created a program with the same logic of the first. On line 10 we check if you are pressing a key, if the answer is yes then we will check which key it is and write an appropriate phrase on the command prompt (terminal), on line 19 we start a code block to do the same action, but when you release the key. To keep it short I only added 3 keys: A, B and C. Run this program and press those keys, press and hold, press different keys, watch what happens.

Now let's learn what each thing in this code is. On line 8 we have 'KeyEvent event', that means we are creating a variable named 'event' of the datatype 'KeyEvent' which is a 'struct' that represents keyboard events. Then on line 10 we have 'if (event.pressed)', here we're just checking if you are pressing or releasing a button because '.pressed' is a 'bool' variable that can be 'true' or 'false' in case you are pressing or releasing a button, respectively. Then on line 12 we have 'if (event.key == Key.A)', this is checking which key you are using because '.key' is a variable that tells the key you are currently using and 'Key' is an 'enum' which contains all of the possible keys a normal keyboard would have (supposing you are using a standard QWERTY English language keyboard). '.A', '.B', and '.C' are only some of the keys inside the 'enum' called 'Key', you can figure out the rest intuitively or you can just check out the full list at http://dpldocs.info/experimental-docs/arsd.simpledisplay.Key.html.

Now, to finish it up, let's learn how to use modifier states, meaning ways to selectively alter the output of your interaction. Let's suppose you want to make a program in which you can type lowercase or upper case letters by holding the Shift key, or a program that draws a line on the screen only when you press and hold the mouse button. The Shift key and the holding of the button are modifier states, they will modify how the event will happen. It is actually super easy to do that. Take a look at the following code.

import arsd.simpledisplay : Key, KeyEvent, ModifierState, SimpleWindow;
import std.stdio : writeln;

void main()
{
    SimpleWindow window = new SimpleWindow(500, 500, "Keyboard input modified");


    window.eventLoop(0,
    delegate (KeyEvent event)
    {
        if (event.pressed)
            if (event.modifierState & ModifierState.shift)
            {
                if (event.key == Key.A)
                    writeln("A");
                else if (event.key == Key.B)
                    writeln("B");
                else if (event.key == Key.C)
                    writeln("C");
            }
            else
            {
                if (event.key == Key.A)
                    writeln("a");
                else if (event.key == Key.B)
                    writeln("b");
                else if (event.key == Key.C)
                    writeln("c");
            }
    });
}

The code above is very similar to the previous. The difference lies in the first 'if' statement because I have a different condition now. I have 'event.modifierState & ModifierState.shift' which simply checks if you are holding Shift or not. '.modifierState' is a variable that contains a value of the type 'ModifierState' which in turn is just an 'enum' with the possible modifier states most computers would have and '.shift' is one of the many modifier states inside it. Pay attention to the fact that I used the bitwise operator '&' because each member of 'ModifierState' is a number which in turn is system dependent and therefore you cannot use the '==' operator otherwise it would not work on all systems. You can see a full list of modifier states on http://dpldocs.info/experimental-docs/arsd.simpledisplay.ModifierState.2.html.

Now let's learn how to use this trick for mouse events. Take a look at the following code.

import arsd.simpledisplay : Color, ModifierState, MouseEvent, MouseEventType, Point, ScreenPainter, SimpleWindow;

void main()
{
    SimpleWindow window = new SimpleWindow(500, 500, "Mouse input modified");


    {window.draw().clear(Color.black());}


    window.eventLoop(0,
    (MouseEvent event)
    {
        ScreenPainter painter = window.draw();
        painter.outlineColor = Color.yellow();


        if (event.type == MouseEventType.motion)
            if (event.modifierState & ModifierState.leftButtonDown)
                painter.drawPixel(Point(event.x, event.y));
    });
}

In this code we just create a window (then we make it black) and its event loop, then we listen for mouse events, then we create our painter and set it to yellow and then we have a condition on line 11 that checks if we are moving the mouse and under it we have another condition checking if the modifier state is the left mouse button down, in which case we will, on line 13, draw a pixel exactly where the arrow is, meaning we have just created a program that allows us to click and drag in order to draw on the window. It doesn't draw if you simply click, you have to really hold and drag. This is the beauty of a modifier state, it allows you to decide under which circumstances an action must happen. The only new element in this code is the '.leftButtonDown' which is just the 'enum' member corresponding to holding down the left mouse button.

One peculiarity about this code is that we make the window black on line 5, but we have placed it between '{ }'. The reason for that is because 'ScreenPainter' only does its job (drawing everything on the window) when it reaches the end of its scope.

Alright, enough of this repetitive shit! You now know how to use the main features of this library and these few tools can be the building blocks of bigger programs. It is up to you to think creatively and figure out how to combine these tools to make magic happen on the screen.

If you liked this document then I would appreciate if you could support the creator of the arsd library by visiting https://www.patreon.com/adam_d_ruppe. And don't forget to follow Mr. Ruppe's GitHub (https://github.com/adamdruppe) to stay updated on everything that comes up.

Comments