shithub: pain

ref: 1c2839d17d4dbe1e5aabd448aa565353ba8cd4af
dir: /pill/pill.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

#define INTERNAL_HEIGHT 640
#define INTERNAL_WIDTH 640
#define STEPS 3600
#define RADIUS .60
#define MAXSATURATION 1.0
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define RAD2DEG(r_) ((180.0)/PI * (r_))
#define DEG2RAD(r_) (PI/(180.0) * (r_))
#define BACKGROUNDCOLOR 0x22272EFF
#define CIRCLESLICE (2*PI/5)
#define CIRCLESHIFT (PI/2 + CIRCLESLICE/2)
#define CIRCLEDIAMETER (2*PI - CIRCLESLICE)
#define COLORPICKERPADDING .10
#define COLORPICKERRADIUS .25
#define INNERBORDERRADIUS	(COLORPICKERPADDING + COLORPICKERRADIUS)
#define RECTANGLE(w, h) ((Rectangle){(Point){(0),(0)}, (Point){(w),(h)}})
#define COLORINDICATORSIZE 0.15
#define VALUEINCREASE 0.01

Image * VirtualWindow = nil;
Image * HuePicker = nil;
Image * Gradient = nil;
u32int * RawGradient = nil;
u32int RawGradientSize = 0;
int DrawGradient = 1;
int DrawHuePicker = 1;
int WindowMoved = 1;

Image * Circle = nil;
uint CircleBPL = 0;
u32int * RawCircle = nil;
uint RawCircleSize = 0;
Image * Background = nil;
float SaturationStart = .50;
float HueStart = PI*.35;
float Value = 1.0;
float Hue = 0.f;
float Saturation = 1.0;
int RunApp = 1;
int UpdateCanvas = 1;
Point MouseGlobalPosition;
Point MouseRelativePosition;
double MouseAngle;
double MouseRadius;
double MouseHueRadiusStart;
double MouseHueRadiusEnd;
int MouseInHue;
int MouseInGradient;
u32int Color;
u32int ColorUnderMouse;
int ShouldDraw = 1;
int UpdateGradient = 1;

int resizeimage(Image * d, Image * s)
{
	int i;
	Point p;
	Image * t, * c;
	float scale;
	Rectangle dr, sr, tr, r;

	dr = rectsubpt(d->r, d->r.min);
	sr = rectsubpt(s->r, s->r.min);

	tr = dr;
	if (Dy(dr) < Dy(sr)) {
		tr.max.y = tr.min.y + Dy(sr);
	}

	if ((t = allocimage(display, tr, s->chan, 1, DTransparent)) == nil) {
		werrstr("allocimage: %r");
		return -1;
	}

	if ((c = allocimage(display, Rect(0, 0, 1, Dy(tr)), s->chan, 1, DNofill)) == nil)
	{
		werrstr("allocimage: %r");
		freeimage(t);
		return -1;
	}

	scale = ((float)Dx(sr))/((float)Dx(tr));

	p = ZP;
	r.min = tr.min;
	r.max.y = tr.max.y;
	
	i = 0;
	while (i < Dx(tr)) {
		p.x = (int)(((float)i)*scale);
		drawop(c, c->r, s, nil, p, S);

		r.min.x = i + tr.min.x;
		r.max.x = r.min.x + 1;
		draw(t, r, c, nil, ZP);
		i+=1;
	}
	
	freeimage(c);

	if ((c = allocimage(display, Rect(0, 0, Dx(tr), 1), s->chan, 1, DNofill)) == nil)
	{
		werrstr("allocimage: %r");
		freeimage(t);
		return -1;
	}

	scale = ((float)Dy(sr))/((float)Dy(dr));

	p = ZP;
	r.min = d->r.min;
	r.max.x = d->r.max.x;

 	i = 0;
 	while (i < Dy(dr)) {
		p.y = (int)(((float)i)*scale);
		drawop(c, c->r, t, nil, p, S);
 		
 		// Move the draw rectangle for the temporary image scale
 		// rectangles to the right
 		r.min.y = i + d->r.min.y;
 		r.max.y = r.min.y + 1;
 		draw(d, r, c, nil, ZP);
 		i+=1;
 	}


	freeimage(t);
	freeimage(c);
	
	return 1;
}

static unsigned int
hsv2rgb(float h, float s, float v)
{
	// Taken from Computer Graphics: Principle and Practice
	unsigned int rgb;
	float f, p, q, t, r = 0.0, g = 0.0, b = 0.0;
	int i;
	
	h = RAD2DEG(h);
	h /= 60.0;
	i = (int)floor(h);
	f = h - i;
	p = v * (1.0 - s);
	q = v * (1.0 - (s * f));
	t = v * (1.0 - (s * (1.0 - f)));

	switch(i)
	{
		case 0:
			r = v;
			g = t;
			b = p;
			break;
		case 1:
			r = q;
			g = v;
			b = p;
			break;
		case 2:
			r = p;
			g = v;
			b = t;
			break;
		case 3:
			r = p;
			g = q;
			b = v;
			break;
		case 4:
			r = t;
			g = p;
			b = v;
			break;
		case 5:
			r = v;
			g = p;
			b = q;
			break;
		default:
			break;
	}

	rgb = (0xff & (uint)(b*255));
	rgb |= (0xff & (uint)(g*255)) << 8;
	rgb |= (0xff & (uint)(r*255)) << 16;
	return (rgb << 8) | 0xff;
}

static void
initgradient(void)
{
	if (Gradient != nil) {
		freeimage(Gradient);
	}

	if (RawGradient != nil) {
		free(RawGradient);
	}
	
	Gradient = allocimage(display, RECTANGLE(INTERNAL_WIDTH, INTERNAL_HEIGHT), RGBA32, 1, DTransparent);
	if(Gradient == nil) {
		sysfatal("initgradient: %r");
	}

	RawGradientSize = bytesperline(Gradient->r, Gradient->depth) * Dy(Gradient->r);
	RawGradient = calloc(RawGradientSize, 1);
	if (RawGradient == nil) {
		sysfatal("calloc: out of memory");
	}
}

static void
setgradienthue(void)
{
	float t, i, l, s, u, ct, st;
	Point c, e;
	int rc, dx, dy;

	if (UpdateGradient == 0) {
		return;
	}

	dx = Dx(Gradient->r);
	dy = Dy(Gradient->r);
	c.x = dx/2 + 1;
	c.y = dy/2 + 1;

	l = MIN(dx, dy);
	l *= RADIUS/2.0;
	
	i = 1/(l*2);
	for (t = 0.0; t < CIRCLEDIAMETER; t += i) {
		u = t/CIRCLEDIAMETER;
		ct = cos(t + CIRCLESHIFT);
		st = sin(t + CIRCLESHIFT);
		for (s = 0; s < l; s += 0.1) {
			e.x = c.x + s * ct;
			e.y = c.y + s * st;
			RawGradient[dx * e.y + e.x] = hsv2rgb(
				HueStart + u, 
				(SaturationStart)+(s/l)*(MAXSATURATION - SaturationStart),
				Value);
		}
	}
	for (t = PI-CIRCLESHIFT; t < (PI - CIRCLESHIFT + CIRCLESLICE); t += i) {
		ct = cos(t);
		st = sin(t);
		for (s = 0; s < l; s += 0.1) {
			e.x = c.x + s * ct;
			e.y = c.y + s * st;
			RawGradient[dx * e.y + e.x] = DWhite;
		}
	}

	rc = loadimage(Gradient, Gradient->r, (uchar *)RawGradient, RawGradientSize);
	if (rc < 0) {
		sysfatal("loadimage: %r");
	}

	UpdateGradient = 0;
}

static void
inithuepicker(void)
{
	int rc, dx, dy, h;
	u32int * raw, rawSize;
	float t, i, s, u, g, st, ct, l;
	Point c;

	if (HuePicker != nil) {
		freeimage(HuePicker);
	}
	
	HuePicker = allocimage(display, RECTANGLE(INTERNAL_WIDTH, INTERNAL_HEIGHT), RGBA32, 1, DTransparent);
	if(HuePicker == nil) {
		sysfatal("inithuepicker: %r");
	}

	rawSize = bytesperline(HuePicker->r, HuePicker->depth) * Dy(HuePicker->r);
	raw = calloc(rawSize, 1);
	if (raw == nil) {
		sysfatal("calloc: out of memory");
	}

	dx = Dx(HuePicker->r);
	dy = Dy(HuePicker->r);
	c.x = dx/2;
	c.y = dy/2;

	l = MIN(dx, dy);
	l *= RADIUS/2.0;

	s = l * (1+COLORPICKERPADDING);
	u = s + (l*COLORPICKERRADIUS);
	i = 1/(u*2);
	for (t = .0; t < 2*PI; t+=i) {
		ct = cos(t);
		st = sin(t);
		
		h = hsv2rgb(t, 1.0, 1.0);
		for (g = s; g < u; g++) {
				raw[dx * (c.y + ((int)(g * st))) + (c.x + ((int)(g * ct)))] = h;
		}
	}

	rc = loadimage(HuePicker, HuePicker->r, (uchar *)raw, rawSize);
	if (rc < 0) {
		sysfatal("loadimage: %r");
	}

	if (raw != nil) {
		free(raw);
	}
}

static void
updatecolorindicator(void)
{
	Image * c;
	Rectangle r;

	if (VirtualWindow == nil) {
		return;
	}

	c = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, Color);
	if (c == nil) {
		sysfatal("allocimage: %r");
	}
	
	r = RECTANGLE( ((float)Dx(screen->r)) * COLORINDICATORSIZE,
		((float)Dx(screen->r)) * COLORINDICATORSIZE);
	r.min = screen->r.min;
	r.max = addpt(r.max, r.min);
	draw(screen, r, c, nil, ZP);
	freeimage(c);
}

u32int
pickcolor(void)
{
	Image * c;
	u32int colors[4];

	if (VirtualWindow == nil) {
		return DWhite;
	}

	//print("r: %R\n", r);
	c = allocimage(display, RECTANGLE(1,1), RGBA32, 1, DNofill);
	if (c == nil) {
		sysfatal("allocimage: %r");
	}
	
	drawop(c, c->r, VirtualWindow, nil, MouseGlobalPosition, S);
	unloadimage(c, c->r, (uchar*)colors, sizeof(colors));
	freeimage(c);

	return colors[0];
}

static void
drawcanvas(void)
{
	if (ShouldDraw == 0) {
		return;
	}
	draw(screen, screen->r, Background, nil, ZP);
	draw(screen, screen->r, VirtualWindow, nil, screen->r.min);
	updatecolorindicator();
	ShouldDraw = 0;
}

void
setbackground(ulong col)
{	
	if (Background != nil)
		freeimage(Background);

	Background = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, col);
	if (Background == nil)
		sysfatal("setbackground: %r");
}

static void
updatecanvas(void)
{
	int rc;

	if (VirtualWindow == nil || 
		Dx(VirtualWindow->r) != Dx(screen->r) || 
		Dy(VirtualWindow->r) != Dx(screen->r) || UpdateCanvas) {

		if (VirtualWindow != nil) {
			freeimage(VirtualWindow);
		}

		VirtualWindow = allocimage(display, screen->r, RGBA32, 0, DTransparent);
		if (VirtualWindow == nil) {
			sysfatal("initcanvas: %r");
		}

	 	rc = resizeimage(VirtualWindow, HuePicker);
	 	if (rc < 0) {
	 		sysfatal("resizeimage: %r");
	 	}

	 	rc = resizeimage(VirtualWindow, Gradient);
	 	if (rc < 0) {
	 		sysfatal("resizeimage: %r");
	 	}
	}
}

static void
initcanvas(void)
{
	setbackground(BACKGROUNDCOLOR);
	ShouldDraw = 1;
}

void
handlemouse(Mouse mouse)
{
	Point c;
	double x, y, v;

	MouseGlobalPosition = mouse.xy;
	MouseRelativePosition = subpt(mouse.xy, screen->r.min);
	c = MouseRelativePosition;
	c.x -= Dx(screen->r)/2.0;
	c.y -= Dy(screen->r)/2;
	x = ((double)c.x)/(Dx(screen->r)/2);
	y = ((double)c.y)/(Dy(screen->r)/2);
	MouseRadius = sqrt(pow(x, 2) + pow(y, 2));
	MouseAngle = atan2(-y, x);
	
	Hue = -MouseAngle + 2*PI;
	if (Hue > 2*PI) {
		Hue -= 2*PI;
	}

	MouseHueRadiusStart = RADIUS * (1+COLORPICKERPADDING);
	MouseHueRadiusEnd = MouseHueRadiusStart + (RADIUS * COLORPICKERRADIUS);
	MouseInHue = MouseRadius >= MouseHueRadiusStart && MouseRadius <= MouseHueRadiusEnd;
	MouseInGradient = MouseRadius <= RADIUS;
	
	ColorUnderMouse = pickcolor();
	if (MouseInGradient && mouse.buttons & 0x01) {
		Color = ColorUnderMouse;
		print("%08x\n", Color);
	}

	if (MouseInHue && mouse.buttons & 0x01) {
		HueStart = Hue;
		UpdateGradient = 1;
		UpdateCanvas = 1;
		ShouldDraw = 1;
	}
	
	v = Value;
	if (mouse.buttons & 0x08) {
		v += VALUEINCREASE;
	}
	if (mouse.buttons & 0x16) {
		v -= VALUEINCREASE;
	}
	if (v != Value) {
		if (v > 1.0) {
			v = 1.0;
		}
		if (v < 0.0) {
			v = 0.0;
		}
		Value = v;
		UpdateGradient = 1;
		UpdateCanvas = 1;
		ShouldDraw = 1;
	}

	updatecolorindicator();
}

void
eresized(int)
{
	if(getwindow(display, Refnone) < 0)
		sysfatal("getwindow: %r");
	initcanvas();
}

void
main(int argc, char * argv[])
{
	Event e;

	USED(argc);
	USED(argv);
	
	if (initdraw(nil, nil, "pill") < 0)
		sysfatal("initdraw: %r\n");

	inithuepicker();
	initgradient();

	einit(Emouse | Ekeyboard);
	initcanvas();
	for (;RunApp;) {
		setgradienthue();
		updatecanvas();
		drawcanvas();
		switch(event(&e)) {
		case Ekeyboard:
			if (e.kbdc == 'q') {
				RunApp = 0;
			}
			break;
		case Emouse:
			handlemouse(e.mouse);
			break;
		}
	}
}