shithub: docs.9front.org

ref: d9fe53c1a4c0abe4d81536a293a844994a12733c
dir: /libdraw-tips.md/

View raw version
# 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.