shithub: wiki.9front.org

Download patch

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.