Advanced Libdraw Tips

Libdraw works by sending remote procedure calls (RPCs) to the draw device. If this is over a network, optimization is done by having the draw device do all of the work, instead of generating each pixel by loop. It’s also useful to allocate all of the images at program initialization, so the interface redraws quicker.

Share your tips below!

Generating Horizontal and Vertical Gradients

Gradients are repeated visuals, and leveraging the repl bit can make generation very quick. If a single gradient is used, it can probably can be generated with the color built in. Otherwise, a mask can be used.

   /* grade my gradient from 0 to nein */
   #include <u.h>
   #include <libc.h>
   #include <draw.h>
   main(int argc, char *argv[])
                 fprint(2, "usage: %s [-b]\n", argv0);
          if(initdraw(nil, nil, argv0) < 0)
                 sysfatal("%s: %r", argv0);
          Image *red, *grn, *blu, *gradx, *grady;
          int dx, dy, i;
          uchar *gx, *gy;
          red = allocimage(display, Rect(0,0,1,1), RGB24, 1, DRed);
          grn = allocimage(display, Rect(0,0,1,1), RGB24, 1, DGreen);
          blu = allocimage(display, Rect(0,0,1,1), RGB24, 1, DBlue);
          dx = Dx(screen->r), dy = Dy(screen->r);
          gradx = allocimage(display, Rect(0,0,dx,1), GREY8, 1, DNofill);
          grady = allocimage(display, Rect(0,0,1,dy), GREY8, 1, DNofill);
          gx = malloc(sizeof(uchar) * dx);
          gy = malloc(sizeof(uchar) * dy);
          if(red == nil || grn == nil || blu == nil || gradx == nil || grady == nil || gx == nil || gy == nil)
                 sysfatal("get more memory dude");
          for(i = 0; i < dx; i++)
                 gx[i] = (uchar)(255.0 * i / (dx-1)); /* sub 1 to make last row 255 */
          for(i = 0; i < dy; i++)
                 gy[i] = (uchar)(255.0 * i / (dy-1));
          loadimage(gradx, gradx->r, gx, dx);
          loadimage(grady, grady->r, gy, dy);
          draw(screen, screen->r, red, nil, ZP);
          draw(screen, screen->r, grn, gradx, ZP);
          draw(screen, screen->r, blu, grady, ZP);
          flushimage(display, Refnone);

Creating and Using Sprites and Animations

What’s that pesky p parameter at the end of the draw function? Why does it move the src image in the wrong direction? That’s because it moves the dst image in terms of the source coordinates! This makes moving images around a little awkward (you have to negate the position for the p parameter), but it makes sprite sheets and animations very intuitive. Conventions!

   /* SPIN */
   #include <u.h>
   #include <libc.h>
   #include <draw.h>

   main(int argc, char *argv[])
                 fprint(2, "usage: %s [-b]\n", argv0);
          if(initdraw(nil, nil, argv0) < 0)
                 sysfatal("%s: %r", argv0);

          Image *red, *grn, *bg, *gradx, *spinner, *spinmask;
          int dx, dy, ds, i, x, y, inc, frame;
          double θ;
          uchar *gx, *gy;
          red = allocimage(display, Rect(0,0,1,1), RGB24, 1, DRed);
          grn = allocimage(display, Rect(0,0,1,1), RGB24, 1, DGreen);
          bg = allocimage(display, Rect(0,0,1,1), RGB24, 1, DNofill);
          dx = Dx(screen->r), dy = Dy(screen->r);
          ds = dx > dy ? dy : dx;
          spinner = allocimage(display, Rect(0,0,ds*60,ds), RGBA32, 0, DTransparent);
          spinmask = allocimage(display, Rect(0,0,ds,ds), GREY1, 0, DWhite);
          gradx = allocimage(display, Rect(0,0,60,1), GREY8, 1, DNofill);
          gx = malloc(sizeof(uchar) * 60);
          if(red == nil || grn == nil || bg == nil || spinner == nil || gradx == nil || gx == nil)
                 sysfatal("get more memory bro");
          for(i = 0; i < 60; i++)
                 gx[i] = (uchar)(255.0 * i / (60-1));
          for(i = 0; i < 60; i++){
                 θ = 2*PI * i / 60;
                 x = (int)((ds/2-5)*cos(θ));
                 y = (int)((ds/2-5)*sin(θ));
                 line(spinner, Pt(x+ds/2+ds*i,y+ds/2), Pt(-x+ds/2+ds*i,-y+ds/2), Endarrow, Endsquare, 1, display->black, ZP);
          loadimage(gradx, gradx->r, gx, dx);
          x = 0;
          frame = 0;
                 draw(bg, bg->r, red, nil, ZP);
                 if(x == 0)
                        inc = 1;
                 else if(x == 60-1)
                        inc = -1;
                 x += inc;
                 gendraw(bg, bg->r, grn, ZP, gradx, Pt(x, 0));
                 draw(screen, screen->r, bg, nil, ZP);     
                 frame = (frame + 1) % 60;
                 gendraw(screen, screen->r, spinner, Pt(frame*ds, 0), spinmask, ZP);
                 flushimage(display, 1);

   rodri : btw, i tried the animation example you wrote in the wiki. i don't fully understand it, but it's awesome
   Amavect : Hmm, I can explain it line by line for ya.
   Amavect : ARG(2)
   Amavect : initdraw gets the name of the program
   rodri : i would love that, thanks
   Amavect : alloc two colors, red and green, they will fill the whole screen due to the repl bit.
   rodri : it's mostly the part with the spinner
   Amavect : bg is the background.
   Amavect : calc dx and dy, figure out a square size by taking the minimum for ds.
   Amavect : spinner is the size of the square, but is 60 times wide! This is to illustrate complex sprites, so that you don't need to recalculate each frame.
   Amavect : The technique is called a sprite sheet.
   Amavect : spinner is the sprite sheet, and the spinmask is used to select the right sprite image.
   Amavect : gradx is used to change the bg color between green and red using alpha masking.
   Amavect : generate the gradient mask, from fully black to fully white, 60 pixels for 60 frames.
   Amavect : (actually, the code is slightly out of sync, we will see that later)
   Amavect : generate the spinner frames:
   Amavect : θ is the angle, of course. A circle is defined by the locus {( cos(θ), sin(θ) ): 0 ≤ θ < 2π}
   Amavect : yay for formal math defs
   rodri : yeah, i love it :). i'm used to that because of the recent work with asteroids and 3d
   Amavect : ds/2-5 is the radius of the circle, purposely slightly less than the smallest side of the window.
   Amavect : line() generates the arrow from (-x,-y) to (x,y), translated to the center point of the square part of the window.
   Amavect : loadimage puts that in the image
   Amavect : draw(red) puts red in the background.
   Amavect : x is used to select the pixel in the gradx that masks the green
   Amavect : gendraw(grn, ZP, gradx, Pt(x,0)) selects the single pixel in grn, and selects the single pixel in the gradient mask to get the right amount of transparency. This is put into the single pixel background.
   Amavect : draw(bg) draws the bg, which fills the screen because of the repl bit.
   Amavect : frame selects the right frame
   Amavect : gendraw(spinner) selects the right frame of the spinner sprite sheet, and puts the mask on top of it so the rest of the sprite sheet doesn't show.
   Amavect : flushimage shows the image
   Amavect : sleep is a simple delay, and really should calculate the difference between the last frame time and the current frame time.
   Amavect : for(ever)
   Amavect : that's all folks
   Amavect : brb
   Amavect : dinner brb in a bit
   rodri : thanks for explaning :D
   rodri : i think this could be dumped into the wiki, as a per-line commentary, for whoever visits the page who doesn't get the sprite generation and neat use of masks (like me ten minutes ago)
   Amavect : Now, you might wonder, what is the purpose of a sprite sheet? Why not have separate images? Historically, sprite sheets are for game opimization, as images must be square and sides a power of 2 (this still happens in OpenGL, iirc). Devdraw certainly is not optimized. Sprite sheets are convenient as "image arrays", and also keep the number of allocated images down.
   Amavect : Lastly, that was my first program implementing a sprite sheet. I know a bunch of things which you all seem impressed with, but have so little experience doing them.

No-one has analyzed yet whether using a sprite sheet along the x, the y, or in a square is the most efficient.

Vertical vs Horizontal Fills

Preliminary experimental results:

I have the following Images:

Filling the full 1010x1010, the horizontal took 16 ms, while the vertical took 22 ms.

The 1010x1010 no repl took 16 ms, the 1010x1010 with repl took 22 ms, despite not needing to be replicated (being drawn onto a 1010x1010 Rectangle).

Possible explanation: cache lines and memory locality. Possible repl magic.

Don’t take this as fact, yet. Please do your own testing.