269 lines
6.6 KiB
Objective-C
269 lines
6.6 KiB
Objective-C
// 6 september 2015
|
|
#include "area.h"
|
|
|
|
struct uiDrawPath {
|
|
CGMutablePathRef path;
|
|
uiDrawFillMode fillMode;
|
|
BOOL ended;
|
|
};
|
|
|
|
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
|
{
|
|
uiDrawPath *p;
|
|
|
|
// TODO uiNew
|
|
p = malloc(sizeof (uiDrawPath));
|
|
p->path = CGPathCreateMutable();
|
|
p->fillMode = mode;
|
|
p->ended = NO;
|
|
return p;
|
|
}
|
|
|
|
void uiDrawFreePath(uiDrawPath *p)
|
|
{
|
|
CGPathRelease((CGPathRef) (p->path));
|
|
// TODO uiFree
|
|
free(p);
|
|
}
|
|
|
|
// TODO
|
|
#define complain(...) ;
|
|
|
|
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
|
{
|
|
if (p->ended)
|
|
complain("attempt to add figure to ended path in uiDrawPathNewFigure()");
|
|
CGPathMoveToPoint(p->path, NULL, x, y);
|
|
}
|
|
|
|
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double endAngle)
|
|
{
|
|
double sinStart, cosStart;
|
|
double startx, starty;
|
|
|
|
if (p->ended)
|
|
complain("attempt to add figure to ended path in uiDrawPathNewFigureWithArc()");
|
|
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, endAngle);
|
|
}
|
|
|
|
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
|
{
|
|
if (p->ended)
|
|
complain("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 endAngle)
|
|
{
|
|
if (p->ended)
|
|
complain("attempt to add arc to ended path in uiDrawPathArcTo()");
|
|
CGPathAddArc(p->path, NULL,
|
|
xCenter, yCenter,
|
|
radius,
|
|
startAngle, endAngle,
|
|
NO);
|
|
}
|
|
|
|
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
|
{
|
|
if (p->ended)
|
|
complain("attempt to add bezier to ended path in uiDrawPathBezierTo()");
|
|
CGPathAddCurveToPoint(p->path, NULL,
|
|
c1x, c1y,
|
|
c2x, c2y,
|
|
endX, endY);
|
|
}
|
|
|
|
void uiDrawPathCloseFigure(uiDrawPath *p)
|
|
{
|
|
if (p->ended)
|
|
complain("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)
|
|
complain("attempt to add rectangle to ended path in uiDrawPathAddRectangle()");
|
|
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
|
|
}
|
|
|
|
void uiDrawPathEnd(uiDrawPath *p)
|
|
{
|
|
p->ended = TRUE;
|
|
}
|
|
|
|
struct uiDrawContext {
|
|
CGContextRef c;
|
|
};
|
|
|
|
uiDrawContext *newContext(CGContextRef ctxt)
|
|
{
|
|
uiDrawContext *c;
|
|
|
|
// TODO use uiNew
|
|
c = (uiDrawContext *) malloc(sizeof (uiDrawContext));
|
|
c->c = ctxt;
|
|
return 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;
|
|
uiDrawPath p2;
|
|
|
|
if (!path->ended)
|
|
complain("path not ended in uiDrawStroke()");
|
|
|
|
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;
|
|
CGContextSetMiterLimit(c->c, p->MiterLimit);
|
|
break;
|
|
case uiDrawLineJoinRound:
|
|
join = kCGLineJoinRound;
|
|
break;
|
|
case uiDrawLineJoinBevel:
|
|
join = kCGLineJoinBevel;
|
|
break;
|
|
}
|
|
|
|
// create a temporary path identical to the previous one
|
|
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
|
|
p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(path->path,
|
|
NULL,
|
|
p->Thickness,
|
|
cap,
|
|
join,
|
|
p->MiterLimit);
|
|
p2.fillMode = path->fillMode;
|
|
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)
|
|
{
|
|
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);
|
|
|
|
// make the gradient
|
|
// TODO uiAlloc
|
|
colors = malloc(b->NumStops * 4 * sizeof (CGFloat));
|
|
locations = malloc(b->NumStops * sizeof (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);
|
|
// TODO uiFree
|
|
free(locations);
|
|
free(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)
|
|
complain("path not ended in uiDrawFill()");
|
|
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;
|
|
}
|
|
complain("unknown brush type %d in uiDrawFill()", b->Type);
|
|
}
|