ref: e5a1ecf7ddaeebce0ebe47638681077b80169e7a
parent: 488cbb480ec16eb03431f7b491275229b368c307
author: kvik <kvik@a-b.xyz>
date: Thu Oct 29 13:18:12 EDT 2020
libdraw-tips: add article from old wiki (thanks Amavect)
--- /dev/null
+++ b/libdraw-tips.md
@@ -1,0 +1,189 @@
+# 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>
+ void
+ main(int argc, char *argv[])
+ {
+ ARGBEGIN{
+ default:
+ fprint(2, "usage: %s [-b]\n", argv0);
+ exits("usage");
+ }ARGEND;
+ 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);
+ sleep(10000);
+ exits(nil);
+ }
+
+## 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 https://www.youtube.com/watch?v=nfPiyKubscs */
+ #include <u.h>
+ #include <libc.h>
+ #include <draw.h>
+
+ void
+ main(int argc, char *argv[])
+ {
+ ARGBEGIN{
+ default:
+ fprint(2, "usage: %s [-b]\n", argv0);
+ exits("usage");
+ }ARGEND;
+ 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;
+ for(;;){
+ 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);
+ sleep(16);
+ }
+ }
+
+ 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:
+
+* A horizontal Image that's 1010x1 with the repl bit set.
+* A vertical Image that's 1x1010 with the repl bit set.
+* An Image that's 1010x1010 without the repl bit set.
+* An Image that's 1010x1010 with the repl bit set.
+
+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.