mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			450 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| // 6 september 2015
 | |
| #import "uipriv_darwin.h"
 | |
| #import "draw.h"
 | |
| 
 | |
| struct uiDrawPath {
 | |
| 	CGMutablePathRef path;
 | |
| 	uiDrawFillMode fillMode;
 | |
| 	BOOL ended;
 | |
| };
 | |
| 
 | |
| uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
 | |
| {
 | |
| 	uiDrawPath *p;
 | |
| 
 | |
| 	p = uiprivNew(uiDrawPath);
 | |
| 	p->path = CGPathCreateMutable();
 | |
| 	p->fillMode = mode;
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| void uiDrawFreePath(uiDrawPath *p)
 | |
| {
 | |
| 	CGPathRelease((CGPathRef) (p->path));
 | |
| 	uiprivFree(p);
 | |
| }
 | |
| 
 | |
| void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
 | |
| {
 | |
| 	if (p->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
 | |
| 	CGPathMoveToPoint(p->path, NULL, x, y);
 | |
| }
 | |
| 
 | |
| void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
 | |
| {
 | |
| 	double sinStart, cosStart;
 | |
| 	double startx, starty;
 | |
| 
 | |
| 	if (p->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
 | |
| 	sinStart = sin(startAngle);
 | |
| 	cosStart = cos(startAngle);
 | |
| 	startx = xCenter + radius * cosStart;
 | |
| 	starty = yCenter + radius * sinStart;
 | |
| 	CGPathMoveToPoint(p->path, NULL, startx, starty);
 | |
| 	uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
 | |
| }
 | |
| 
 | |
| void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
 | |
| {
 | |
| 	// TODO refine this to require being in a path
 | |
| 	if (p->ended)
 | |
| 		uiprivImplBug("attempt to add line to ended path in uiDrawPathLineTo()");
 | |
| 	CGPathAddLineToPoint(p->path, NULL, x, y);
 | |
| }
 | |
| 
 | |
| void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
 | |
| {
 | |
| 	bool cw;
 | |
| 
 | |
| 	// TODO likewise
 | |
| 	if (p->ended)
 | |
| 		uiprivImplBug("attempt to add arc to ended path in uiDrawPathArcTo()");
 | |
| 	if (sweep > 2 * uiPi)
 | |
| 		sweep = 2 * uiPi;
 | |
| 	cw = false;
 | |
| 	if (negative)
 | |
| 		cw = true;
 | |
| 	CGPathAddArc(p->path, NULL,
 | |
| 		xCenter, yCenter,
 | |
| 		radius,
 | |
| 		startAngle, startAngle + sweep,
 | |
| 		cw);
 | |
| }
 | |
| 
 | |
| void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
 | |
| {
 | |
| 	// TODO likewise
 | |
| 	if (p->ended)
 | |
| 		uiprivImplBug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
 | |
| 	CGPathAddCurveToPoint(p->path, NULL,
 | |
| 		c1x, c1y,
 | |
| 		c2x, c2y,
 | |
| 		endX, endY);
 | |
| }
 | |
| 
 | |
| void uiDrawPathCloseFigure(uiDrawPath *p)
 | |
| {
 | |
| 	// TODO likewise
 | |
| 	if (p->ended)
 | |
| 		uiprivImplBug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
 | |
| 	CGPathCloseSubpath(p->path);
 | |
| }
 | |
| 
 | |
| void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
 | |
| {
 | |
| 	if (p->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
 | |
| 	CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
 | |
| }
 | |
| 
 | |
| void uiDrawPathEnd(uiDrawPath *p)
 | |
| {
 | |
| 	p->ended = TRUE;
 | |
| }
 | |
| 
 | |
| uiDrawContext *uiprivDrawNewContext(CGContextRef ctxt, CGFloat height)
 | |
| {
 | |
| 	uiDrawContext *c;
 | |
| 
 | |
| 	c = uiprivNew(uiDrawContext);
 | |
| 	c->c = ctxt;
 | |
| 	c->height = height;
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| void uiprivDrawFreeContext(uiDrawContext *c)
 | |
| {
 | |
| 	uiprivFree(c);
 | |
| }
 | |
| 
 | |
| // a stroke is identical to a fill of a stroked path
 | |
| // we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
 | |
| // doing this for other brushes works too
 | |
| void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
 | |
| {
 | |
| 	CGLineCap cap;
 | |
| 	CGLineJoin join;
 | |
| 	CGPathRef dashPath;
 | |
| 	CGFloat *dashes;
 | |
| 	size_t i;
 | |
| 	uiDrawPath p2;
 | |
| 
 | |
| 	if (!path->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
 | |
| 
 | |
| 	switch (p->Cap) {
 | |
| 	case uiDrawLineCapFlat:
 | |
| 		cap = kCGLineCapButt;
 | |
| 		break;
 | |
| 	case uiDrawLineCapRound:
 | |
| 		cap = kCGLineCapRound;
 | |
| 		break;
 | |
| 	case uiDrawLineCapSquare:
 | |
| 		cap = kCGLineCapSquare;
 | |
| 		break;
 | |
| 	}
 | |
| 	switch (p->Join) {
 | |
| 	case uiDrawLineJoinMiter:
 | |
| 		join = kCGLineJoinMiter;
 | |
| 		break;
 | |
| 	case uiDrawLineJoinRound:
 | |
| 		join = kCGLineJoinRound;
 | |
| 		break;
 | |
| 	case uiDrawLineJoinBevel:
 | |
| 		join = kCGLineJoinBevel;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	// create a temporary path identical to the previous one
 | |
| 	dashPath = (CGPathRef) path->path;
 | |
| 	if (p->NumDashes != 0) {
 | |
| 		dashes = (CGFloat *) uiprivAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
 | |
| 		for (i = 0; i < p->NumDashes; i++)
 | |
| 			dashes[i] = p->Dashes[i];
 | |
| 		dashPath = CGPathCreateCopyByDashingPath(path->path,
 | |
| 			NULL,
 | |
| 			p->DashPhase,
 | |
| 			dashes,
 | |
| 			p->NumDashes);
 | |
| 		uiprivFree(dashes);
 | |
| 	}
 | |
| 	// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
 | |
| 	// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
 | |
| 	p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
 | |
| 		NULL,
 | |
| 		p->Thickness,
 | |
| 		cap,
 | |
| 		join,
 | |
| 		p->MiterLimit);
 | |
| 	if (p->NumDashes != 0)
 | |
| 		CGPathRelease(dashPath);
 | |
| 
 | |
| 	// always draw stroke fills using the winding rule
 | |
| 	// otherwise intersecting figures won't draw correctly
 | |
| 	p2.fillMode = uiDrawFillModeWinding;
 | |
| 	p2.ended = path->ended;
 | |
| 	uiDrawFill(c, &p2, b);
 | |
| 	// and clean up
 | |
| 	CGPathRelease((CGPathRef) (p2.path));
 | |
| }
 | |
| 
 | |
| // for a solid fill, we can merely have Core Graphics fill directly
 | |
| static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
 | |
| {
 | |
| 	// TODO this uses DeviceRGB; switch to sRGB
 | |
| 	CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
 | |
| 	switch (p->fillMode) {
 | |
| 	case uiDrawFillModeWinding:
 | |
| 		CGContextFillPath(ctxt);
 | |
| 		break;
 | |
| 	case uiDrawFillModeAlternate:
 | |
| 		CGContextEOFillPath(ctxt);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // for a gradient fill, we need to clip to the path and then draw the gradient
 | |
| // see http://stackoverflow.com/a/25034854/3408572
 | |
| static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
 | |
| {
 | |
| 	CGGradientRef gradient;
 | |
| 	CGColorSpaceRef colorspace;
 | |
| 	CGFloat *colors;
 | |
| 	CGFloat *locations;
 | |
| 	size_t i;
 | |
| 
 | |
| 	// gradients need a color space
 | |
| 	// for consistency with windows, use sRGB
 | |
| 	colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
 | |
| 	if (colorspace == NULL) {
 | |
| 		// TODO
 | |
| 	}
 | |
| 	// TODO add NULL check to other uses of CGColorSpace
 | |
| 
 | |
| 	// make the gradient
 | |
| 	colors = uiprivAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
 | |
| 	locations = uiprivAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
 | |
| 	for (i = 0; i < b->NumStops; i++) {
 | |
| 		colors[i * 4 + 0] = b->Stops[i].R;
 | |
| 		colors[i * 4 + 1] = b->Stops[i].G;
 | |
| 		colors[i * 4 + 2] = b->Stops[i].B;
 | |
| 		colors[i * 4 + 3] = b->Stops[i].A;
 | |
| 		locations[i] = b->Stops[i].Pos;
 | |
| 	}
 | |
| 	gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
 | |
| 	uiprivFree(locations);
 | |
| 	uiprivFree(colors);
 | |
| 
 | |
| 	// because we're mucking with clipping, we need to save the graphics state and restore it later
 | |
| 	CGContextSaveGState(ctxt);
 | |
| 
 | |
| 	// clip
 | |
| 	switch (p->fillMode) {
 | |
| 	case uiDrawFillModeWinding:
 | |
| 		CGContextClip(ctxt);
 | |
| 		break;
 | |
| 	case uiDrawFillModeAlternate:
 | |
| 		CGContextEOClip(ctxt);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	// draw the gradient
 | |
| 	switch (b->Type) {
 | |
| 	case uiDrawBrushTypeLinearGradient:
 | |
| 		CGContextDrawLinearGradient(ctxt,
 | |
| 			gradient,
 | |
| 			CGPointMake(b->X0, b->Y0),
 | |
| 			CGPointMake(b->X1, b->Y1),
 | |
| 			kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
 | |
| 		break;
 | |
| 	case uiDrawBrushTypeRadialGradient:
 | |
| 		CGContextDrawRadialGradient(ctxt,
 | |
| 			gradient,
 | |
| 			CGPointMake(b->X0, b->Y0),
 | |
| 			// make the start circle radius 0 to make it a point
 | |
| 			0,
 | |
| 			CGPointMake(b->X1, b->Y1),
 | |
| 			b->OuterRadius,
 | |
| 			kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	// and clean up
 | |
| 	CGContextRestoreGState(ctxt);
 | |
| 	CGGradientRelease(gradient);
 | |
| 	CGColorSpaceRelease(colorspace);
 | |
| }
 | |
| 
 | |
| void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
 | |
| {
 | |
| 	if (!path->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
 | |
| 	CGContextAddPath(c->c, (CGPathRef) (path->path));
 | |
| 	switch (b->Type) {
 | |
| 	case uiDrawBrushTypeSolid:
 | |
| 		fillSolid(c->c, path, b);
 | |
| 		return;
 | |
| 	case uiDrawBrushTypeLinearGradient:
 | |
| 	case uiDrawBrushTypeRadialGradient:
 | |
| 		fillGradient(c->c, path, b);
 | |
| 		return;
 | |
| //	case uiDrawBrushTypeImage:
 | |
| 		// TODO
 | |
| 		return;
 | |
| 	}
 | |
| 	uiprivUserBug("Unknown brush type %d passed to uiDrawFill().", b->Type);
 | |
| }
 | |
| 
 | |
| static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
 | |
| {
 | |
| 	c->a = m->M11;
 | |
| 	c->b = m->M12;
 | |
| 	c->c = m->M21;
 | |
| 	c->d = m->M22;
 | |
| 	c->tx = m->M31;
 | |
| 	c->ty = m->M32;
 | |
| }
 | |
| 
 | |
| static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
 | |
| {
 | |
| 	m->M11 = c->a;
 | |
| 	m->M12 = c->b;
 | |
| 	m->M21 = c->c;
 | |
| 	m->M22 = c->d;
 | |
| 	m->M31 = c->tx;
 | |
| 	m->M32 = c->ty;
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	c = CGAffineTransformTranslate(c, x, y);
 | |
| 	c2m(&c, m);
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 	double xt, yt;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	xt = x;
 | |
| 	yt = y;
 | |
| 	uiprivScaleCenter(xCenter, yCenter, &xt, &yt);
 | |
| 	c = CGAffineTransformTranslate(c, xt, yt);
 | |
| 	c = CGAffineTransformScale(c, x, y);
 | |
| 	c = CGAffineTransformTranslate(c, -xt, -yt);
 | |
| 	c2m(&c, m);
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	c = CGAffineTransformTranslate(c, x, y);
 | |
| 	c = CGAffineTransformRotate(c, amount);
 | |
| 	c = CGAffineTransformTranslate(c, -x, -y);
 | |
| 	c2m(&c, m);
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
 | |
| {
 | |
| 	uiprivFallbackSkew(m, x, y, xamount, yamount);
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 	CGAffineTransform d;
 | |
| 
 | |
| 	m2c(dest, &c);
 | |
| 	m2c(src, &d);
 | |
| 	c = CGAffineTransformConcat(c, d);
 | |
| 	c2m(&c, dest);
 | |
| }
 | |
| 
 | |
| // there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
 | |
| // therefore, special care must be taken to catch matrices who are their own inverses
 | |
| // TODO figure out which matrices these are and do so
 | |
| int uiDrawMatrixInvertible(uiDrawMatrix *m)
 | |
| {
 | |
| 	CGAffineTransform c, d;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	d = CGAffineTransformInvert(c);
 | |
| 	return CGAffineTransformEqualToTransform(c, d) == false;
 | |
| }
 | |
| 
 | |
| int uiDrawMatrixInvert(uiDrawMatrix *m)
 | |
| {
 | |
| 	CGAffineTransform c, d;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	d = CGAffineTransformInvert(c);
 | |
| 	if (CGAffineTransformEqualToTransform(c, d))
 | |
| 		return 0;
 | |
| 	c2m(&d, m);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 	CGPoint p;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
 | |
| 	*x = p.x;
 | |
| 	*y = p.y;
 | |
| }
 | |
| 
 | |
| void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
 | |
| {
 | |
| 	CGAffineTransform c;
 | |
| 	CGSize s;
 | |
| 
 | |
| 	m2c(m, &c);
 | |
| 	s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
 | |
| 	*x = s.width;
 | |
| 	*y = s.height;
 | |
| }
 | |
| 
 | |
| void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
 | |
| {
 | |
| 	CGAffineTransform cm;
 | |
| 
 | |
| 	m2c(m, &cm);
 | |
| 	CGContextConcatCTM(c->c, cm);
 | |
| }
 | |
| 
 | |
| void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
 | |
| {
 | |
| 	if (!path->ended)
 | |
| 		uiprivUserBug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
 | |
| 	CGContextAddPath(c->c, (CGPathRef) (path->path));
 | |
| 	switch (path->fillMode) {
 | |
| 	case uiDrawFillModeWinding:
 | |
| 		CGContextClip(c->c);
 | |
| 		break;
 | |
| 	case uiDrawFillModeAlternate:
 | |
| 		CGContextEOClip(c->c);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO figure out what besides transforms these save/restore on all platforms
 | |
| void uiDrawSave(uiDrawContext *c)
 | |
| {
 | |
| 	CGContextSaveGState(c->c);
 | |
| }
 | |
| 
 | |
| void uiDrawRestore(uiDrawContext *c)
 | |
| {
 | |
| 	CGContextRestoreGState(c->c);
 | |
| }
 |