nvg: API for 3-color linear gradients; multistop linear gradient optimizations

This commit is contained in:
Ketmar Dark 2018-03-05 06:47:34 +02:00 committed by Adam D. Ruppe
parent d41a95dc26
commit 10766f14fa
1 changed files with 497 additions and 173 deletions

View File

@ -1068,7 +1068,9 @@ public struct NVGPaint {
float radius = 0.0f; float radius = 0.0f;
float feather = 0.0f; float feather = 0.0f;
NVGColor innerColor; /// this can be used to modulate images (fill/font) NVGColor innerColor; /// this can be used to modulate images (fill/font)
NVGColor middleColor;
NVGColor outerColor; NVGColor outerColor;
float midp = -1; // middle stop for 3-color gradient
NVGImage image; NVGImage image;
bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint
@ -1078,6 +1080,8 @@ public struct NVGPaint {
radius = p.radius; radius = p.radius;
feather = p.feather; feather = p.feather;
innerColor = p.innerColor; innerColor = p.innerColor;
middleColor = p.middleColor;
midp = p.midp;
outerColor = p.outerColor; outerColor = p.outerColor;
image = p.image; image = p.image;
simpleColor = p.simpleColor; simpleColor = p.simpleColor;
@ -1089,6 +1093,8 @@ public struct NVGPaint {
radius = p.radius; radius = p.radius;
feather = p.feather; feather = p.feather;
innerColor = p.innerColor; innerColor = p.innerColor;
middleColor = p.middleColor;
midp = p.midp;
outerColor = p.outerColor; outerColor = p.outerColor;
image = p.image; image = p.image;
simpleColor = p.simpleColor; simpleColor = p.simpleColor;
@ -1498,15 +1504,21 @@ public alias NVGContext = NVGcontextinternal*;
// Returns FontStash context of the given NanoVega context. // Returns FontStash context of the given NanoVega context.
public FONScontext* fonsContext (NVGContext ctx) { return (ctx !is null ? ctx.fs : null); } public FONScontext* fonsContext (NVGContext ctx) { return (ctx !is null ? ctx.fs : null); }
/** Bezier curve tesselator. /** Bezier curve rasterizer.
* *
* De Casteljau Bezier tesselator is faster, but currently rasterizing curves with cusps sligtly wrong. * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong.
* It doesn't really matter in practice. * It doesn't really matter in practice.
* *
* AFD tesselator is somewhat slower, but does cusps better. */ * AFD tesselator is somewhat slower, but does cusps better.
*
* McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will
* never notice any visial difference (and this code is not really debugged), so you probably should
* not use it. It is there for further experiments.
*/
public enum NVGTesselation { public enum NVGTesselation {
DeCasteljau, /// default: standard well-known tesselation algorithm DeCasteljau, /// default: standard well-known tesselation algorithm
AFD, /// adaptive forward differencing AFD, /// adaptive forward differencing
DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results
} }
/// Default tesselator for Bezier curves. /// Default tesselator for Bezier curves.
@ -1570,9 +1582,11 @@ private:
NVGstate[NVG_MAX_STATES] states; NVGstate[NVG_MAX_STATES] states;
int nstates; int nstates;
NVGpathCache* cache; NVGpathCache* cache;
float tessTol; public float tessTol;
public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer
public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps)
float distTol; float distTol;
float fringeWidth; public float fringeWidth;
float devicePxRatio; float devicePxRatio;
FONScontext* fs; FONScontext* fs;
NVGImage[NVG_MAX_FONTIMAGES] fontImages; NVGImage[NVG_MAX_FONTIMAGES] fontImages;
@ -1745,6 +1759,9 @@ NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc {
if (ctx is null) goto error; if (ctx is null) goto error;
memset(ctx, 0, NVGcontextinternal.sizeof); memset(ctx, 0, NVGcontextinternal.sizeof);
ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer
ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps)
ctx.contextAlive = true; ctx.contextAlive = true;
ctx.params = *params; ctx.params = *params;
@ -2035,9 +2052,11 @@ private:
// apply global alpha // apply global alpha
fillPaint.innerColor.a *= state.alpha; fillPaint.innerColor.a *= state.alpha;
fillPaint.middleColor.a *= state.alpha;
fillPaint.outerColor.a *= state.alpha; fillPaint.outerColor.a *= state.alpha;
fillPaint.innerColor.applyTint(fillTint); fillPaint.innerColor.applyTint(fillTint);
fillPaint.middleColor.applyTint(fillTint);
fillPaint.outerColor.applyTint(fillTint); fillPaint.outerColor.applyTint(fillTint);
ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode);
@ -2055,13 +2074,16 @@ private:
NVGPaint strokePaint = node.paint; NVGPaint strokePaint = node.paint;
strokePaint.innerColor.a *= cc.strokeAlphaMul; strokePaint.innerColor.a *= cc.strokeAlphaMul;
strokePaint.middleColor.a *= cc.strokeAlphaMul;
strokePaint.outerColor.a *= cc.strokeAlphaMul; strokePaint.outerColor.a *= cc.strokeAlphaMul;
// apply global alpha // apply global alpha
strokePaint.innerColor.a *= state.alpha; strokePaint.innerColor.a *= state.alpha;
strokePaint.middleColor.a *= state.alpha;
strokePaint.outerColor.a *= state.alpha; strokePaint.outerColor.a *= state.alpha;
strokePaint.innerColor.applyTint(strokeTint); strokePaint.innerColor.applyTint(strokeTint);
strokePaint.middleColor.applyTint(strokeTint);
strokePaint.outerColor.applyTint(strokeTint); strokePaint.outerColor.applyTint(strokeTint);
ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths);
@ -2754,8 +2776,8 @@ void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @
p.xform.identity; p.xform.identity;
p.radius = 0.0f; p.radius = 0.0f;
p.feather = 1.0f; p.feather = 1.0f;
p.innerColor = color; p.innerColor = p.middleColor = p.outerColor = color;
p.outerColor = color; p.midp = -1;
p.simpleColor = true; p.simpleColor = true;
} }
@ -2912,14 +2934,14 @@ public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
/// Sets current stroke style to a solid color. /// Sets current stroke style to a solid color.
/// Group: render_styles /// Group: render_styles
public void strokeColor (NVGContext ctx, NVGColor color) nothrow @trusted @nogc { public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
NVGstate* state = nvg__getState(ctx); NVGstate* state = nvg__getState(ctx);
nvg__setPaintColor(state.stroke, color); nvg__setPaintColor(state.stroke, color);
} }
/// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
/// Group: render_styles /// Group: render_styles
public void strokePaint (NVGContext ctx, NVGPaint paint) nothrow @trusted @nogc { public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
NVGstate* state = nvg__getState(ctx); NVGstate* state = nvg__getState(ctx);
state.stroke = paint; state.stroke = paint;
//nvgTransformMultiply(state.stroke.xform[], state.xform[]); //nvgTransformMultiply(state.stroke.xform[], state.xform[]);
@ -2937,20 +2959,36 @@ public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc {
/// Sets current fill style to a solid color. /// Sets current fill style to a solid color.
/// Group: render_styles /// Group: render_styles
public void fillColor (NVGContext ctx, NVGColor color) nothrow @trusted @nogc { public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc {
NVGstate* state = nvg__getState(ctx); NVGstate* state = nvg__getState(ctx);
nvg__setPaintColor(state.fill, color); nvg__setPaintColor(state.fill, color);
} }
/// Sets current fill style to a paint, which can be a one of the gradients or a pattern. /// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
/// Group: render_styles /// Group: render_styles
public void fillPaint (NVGContext ctx, NVGPaint paint) nothrow @trusted @nogc { public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc {
NVGstate* state = nvg__getState(ctx); NVGstate* state = nvg__getState(ctx);
state.fill = paint; state.fill = paint;
//nvgTransformMultiply(state.fill.xform[], state.xform[]); //nvgTransformMultiply(state.fill.xform[], state.xform[]);
state.fill.xform.mul(state.xform); state.fill.xform.mul(state.xform);
} }
/// Sets current fill style to a multistop linear gradient.
/// Group: render_styles
public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc {
if (!lgs.valid) {
NVGPaint p = void;
memset(&p, 0, p.sizeof);
nvg__setPaintColor(p, NVGColor.red);
ctx.fillPaint = p;
} else if (lgs.midp >= -1) {
//{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); }
ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
} else {
ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
}
}
/// Returns current transformation matrix. /// Returns current transformation matrix.
/// Group: render_transformations /// Group: render_transformations
public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc {
@ -3164,6 +3202,16 @@ static if (NanoVegaHasArsdColor) {
public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, in Color icol, in Color ocol) nothrow @trusted @nogc { public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, in Color icol, in Color ocol) nothrow @trusted @nogc {
return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol));
} }
/** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
* and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
* range `(0..1)`, and ocol the end color.
* The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
*
* Group: paints
*/
public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, in Color icol, in float midp, in Color mcol, in Color ocol) nothrow @trusted @nogc {
return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol));
}
} }
/** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates
@ -3172,7 +3220,7 @@ public NVGPaint linearGradient (NVGContext ctx, in float sx, in float sy, in flo
* *
* Group: paints * Group: paints
*/ */
public NVGPaint linearGradient (NVGContext ctx, float sx, float sy, float ex, float ey, NVGColor icol, NVGColor ocol) nothrow @trusted @nogc { public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
enum large = 1e5f; enum large = 1e5f;
NVGPaint p = void; NVGPaint p = void;
@ -3202,7 +3250,61 @@ public NVGPaint linearGradient (NVGContext ctx, float sx, float sy, float ex, fl
p.feather = nvg__max(NVG_MIN_FEATHER, d); p.feather = nvg__max(NVG_MIN_FEATHER, d);
p.innerColor = p.middleColor = icol;
p.outerColor = ocol;
p.midp = -1;
return p;
}
/** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start
* and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in
* range `(0..1)`, and ocol the end color.
* The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint].
*
* Group: paints
*/
public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in float midp, in auto ref NVGColor mcol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
enum large = 1e5f;
NVGPaint p = void;
memset(&p, 0, p.sizeof);
p.simpleColor = false;
// Calculate transform aligned to the line
float dx = ex-sx;
float dy = ey-sy;
immutable float d = nvg__sqrtf(dx*dx+dy*dy);
if (d > 0.0001f) {
dx /= d;
dy /= d;
} else {
dx = 0;
dy = 1;
}
p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx;
p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy;
p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large;
p.extent.ptr[0] = large;
p.extent.ptr[1] = large+d*0.5f;
p.radius = 0.0f;
p.feather = nvg__max(NVG_MIN_FEATHER, d);
if (midp <= 0) {
p.innerColor = p.middleColor = mcol;
p.midp = -1;
} else if (midp > 1) {
p.innerColor = p.middleColor = icol;
p.midp = -1;
} else {
p.innerColor = icol; p.innerColor = icol;
p.middleColor = mcol;
p.midp = midp;
}
p.outerColor = ocol; p.outerColor = ocol;
return p; return p;
@ -3226,7 +3328,7 @@ public NVGPaint radialGradient (NVGContext ctx, in float cx, in float cy, in flo
* *
* Group: paints * Group: paints
*/ */
public NVGPaint radialGradient (NVGContext ctx, float cx, float cy, float inr, float outr, NVGColor icol, NVGColor ocol) nothrow @trusted @nogc { public NVGPaint radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
immutable float r = (inr+outr)*0.5f; immutable float r = (inr+outr)*0.5f;
immutable float f = (outr-inr); immutable float f = (outr-inr);
@ -3245,8 +3347,9 @@ public NVGPaint radialGradient (NVGContext ctx, float cx, float cy, float inr, f
p.feather = nvg__max(NVG_MIN_FEATHER, f); p.feather = nvg__max(NVG_MIN_FEATHER, f);
p.innerColor = icol; p.innerColor = p.middleColor = icol;
p.outerColor = ocol; p.outerColor = ocol;
p.midp = -1;
return p; return p;
} }
@ -3273,7 +3376,7 @@ public NVGPaint boxGradient (NVGContext ctx, in float x, in float y, in float w,
* *
* Group: paints * Group: paints
*/ */
public NVGPaint boxGradient (NVGContext ctx, float x, float y, float w, float h, float r, float f, NVGColor icol, NVGColor ocol) nothrow @trusted @nogc { public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc {
NVGPaint p = void; NVGPaint p = void;
memset(&p, 0, p.sizeof); memset(&p, 0, p.sizeof);
p.simpleColor = false; p.simpleColor = false;
@ -3289,8 +3392,9 @@ public NVGPaint boxGradient (NVGContext ctx, float x, float y, float w, float h,
p.feather = nvg__max(NVG_MIN_FEATHER, f); p.feather = nvg__max(NVG_MIN_FEATHER, f);
p.innerColor = icol; p.innerColor = p.middleColor = icol;
p.outerColor = ocol; p.outerColor = ocol;
p.midp = -1;
return p; return p;
} }
@ -3315,7 +3419,8 @@ public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, flo
p.image = image; p.image = image;
p.innerColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha);
p.midp = -1;
return p; return p;
} }
@ -3325,14 +3430,17 @@ public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, flo
/// Group: paints /// Group: paints
public struct NVGLGS { public struct NVGLGS {
private: private:
NVGColor ic, mc, oc; // inner, middle, out
float midp;
NVGImage imgid; NVGImage imgid;
// [imagePattern] arguments // [imagePattern] arguments
float cx, cy, dim, angle; float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients
public float angle; ///
public: public:
@disable this (this); // no copies @disable this (this); // no copies
@property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return imgid.valid; } /// @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } ///
void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); } /// void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } ///
} }
/** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops].
@ -3347,8 +3455,10 @@ public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trus
memset(&p, 0, p.sizeof); memset(&p, 0, p.sizeof);
nvg__setPaintColor(p, NVGColor.red); nvg__setPaintColor(p, NVGColor.red);
return p; return p;
} else if (lgs.midp >= -1) {
return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc);
} else { } else {
return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dim, lgs.dim, lgs.angle, lgs.imgid); return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid);
} }
} }
@ -3359,9 +3469,10 @@ public struct NVGGradientStop {
float offset = 0; /// [0..1] float offset = 0; /// [0..1]
NVGColor color; /// NVGColor color; ///
/// this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } ///
this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } static if (NanoVegaHasArsdColor) {
this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } ///
}
} }
/// Create linear gradient data suitable to use with `linearGradient(res)`. /// Create linear gradient data suitable to use with `linearGradient(res)`.
@ -3401,14 +3512,32 @@ public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in flo
} }
NVGLGS res; NVGLGS res;
res.cx = sx;
res.cy = sy;
if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) {
// create simple linear gradient
res.ic = res.mc = stops.ptr[0].color;
res.oc = stops.ptr[1].color;
res.midp = -1;
res.dimx = ex;
res.dimy = ey;
} else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) {
// create simple linear gradient with middle stop
res.ic = stops.ptr[0].color;
res.mc = stops.ptr[1].color;
res.oc = stops.ptr[2].color;
res.midp = stops.ptr[1].offset;
res.dimx = ex;
res.dimy = ey;
} else {
// create image gradient
uint[NVG_GRADIENT_SAMPLES] data = void; uint[NVG_GRADIENT_SAMPLES] data = void;
immutable float w = ex-sx; immutable float w = ex-sx;
immutable float h = ey-sy; immutable float h = ey-sy;
res.dim = nvg__sqrtf(w*w+h*h); res.dimx = nvg__sqrtf(w*w+h*h);
res.dimy = 1; //???
res.cx = sx;
res.cy = sy;
res.angle = res.angle =
(/*nvg__absf(h) < 0.0001 ? 0 : (/*nvg__absf(h) < 0.0001 ? 0 :
nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/
@ -3425,7 +3554,8 @@ public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in flo
gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1));
foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1);
gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1);
res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[], NVGImageFlag.RepeatX, NVGImageFlag.RepeatY); res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/);
}
} }
return res; return res;
} }
@ -3877,6 +4007,176 @@ void nvg__tesselateBezier (NVGContext ctx, in float x1, in float y1, in float x2
nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
} }
// based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro!
// see http://www.antigrain.com/research/adaptive_bezier/index.html
void nvg__tesselateBezierMcSeem (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float x3, in float y3, in float x4, in float y4, in int level, in int type) nothrow @trusted @nogc {
enum CollinearEPS = 0.00000001f; // 0.00001f;
enum AngleTolEPS = 0.01f;
static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc {
pragma(inline, true);
immutable float dx = x2-x1;
immutable float dy = y2-y1;
return dx*dx+dy*dy;
}
if (level == 0) {
nvg__addPoint(ctx, x1, y1, 0);
nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type);
nvg__addPoint(ctx, x4, y4, type);
return;
}
if (level >= 32) return; // recurse limit; practically, it should be never reached, but...
// calculate all the mid-points of the line segments
immutable float x12 = (x1+x2)*0.5f;
immutable float y12 = (y1+y2)*0.5f;
immutable float x23 = (x2+x3)*0.5f;
immutable float y23 = (y2+y3)*0.5f;
immutable float x34 = (x3+x4)*0.5f;
immutable float y34 = (y3+y4)*0.5f;
immutable float x123 = (x12+x23)*0.5f;
immutable float y123 = (y12+y23)*0.5f;
immutable float x234 = (x23+x34)*0.5f;
immutable float y234 = (y23+y34)*0.5f;
immutable float x1234 = (x123+x234)*0.5f;
immutable float y1234 = (y123+y234)*0.5f;
// try to approximate the full cubic curve by a single straight line
immutable float dx = x4-x1;
immutable float dy = y4-y1;
float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx));
float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx));
//immutable float da1, da2, k;
final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) {
case 0:
// all collinear or p1 == p4
float k = dx*dx+dy*dy;
if (k == 0) {
d2 = distSquared(x1, y1, x2, y2);
d3 = distSquared(x4, y4, x3, y3);
} else {
k = 1.0f/k;
float da1 = x2-x1;
float da2 = y2-y1;
d2 = k*(da1*dx+da2*dy);
da1 = x3-x1;
da2 = y3-y1;
d3 = k*(da1*dx+da2*dy);
if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
// Simple collinear case, 1---2---3---4
// We can leave just two endpoints
return;
}
if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1);
else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4);
else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy);
if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1);
else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4);
else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy);
}
if (d2 > d3) {
if (d2 < ctx.tessTol) {
nvg__addPoint(ctx, x2, y2, type);
return;
}
} if (d3 < ctx.tessTol) {
nvg__addPoint(ctx, x3, y3, type);
return;
}
break;
case 1:
// p1,p2,p4 are collinear, p3 is significant
if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) {
if (ctx.angleTol < AngleTolEPS) {
nvg__addPoint(ctx, x23, y23, type);
return;
} else {
// angle condition
float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2));
if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
if (da1 < ctx.angleTol) {
nvg__addPoint(ctx, x2, y2, type);
nvg__addPoint(ctx, x3, y3, type);
return;
}
if (ctx.cuspLimit != 0.0) {
if (da1 > ctx.cuspLimit) {
nvg__addPoint(ctx, x3, y3, type);
return;
}
}
}
}
break;
case 2:
// p1,p3,p4 are collinear, p2 is significant
if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) {
if (ctx.angleTol < AngleTolEPS) {
nvg__addPoint(ctx, x23, y23, type);
return;
} else {
// angle condition
float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1));
if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
if (da1 < ctx.angleTol) {
nvg__addPoint(ctx, x2, y2, type);
nvg__addPoint(ctx, x3, y3, type);
return;
}
if (ctx.cuspLimit != 0.0) {
if (da1 > ctx.cuspLimit) {
nvg__addPoint(ctx, x2, y2, type);
return;
}
}
}
}
break;
case 3:
// regular case
if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) {
// if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions
if (ctx.angleTol < AngleTolEPS) {
nvg__addPoint(ctx, x23, y23, type);
return;
} else {
// angle and cusp condition
immutable float k = nvg__atan2f(y3-y2, x3-x2);
float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1));
float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k);
if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1;
if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2;
if (da1+da2 < ctx.angleTol) {
// finally we can stop the recursion
nvg__addPoint(ctx, x23, y23, type);
return;
}
if (ctx.cuspLimit != 0.0) {
if (da1 > ctx.cuspLimit) {
nvg__addPoint(ctx, x2, y2, type);
return;
}
if (da2 > ctx.cuspLimit) {
nvg__addPoint(ctx, x3, y3, type);
return;
}
}
}
}
break;
}
// continue subdivision
nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0);
nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type);
}
// Adaptive forward differencing for bezier tesselation. // Adaptive forward differencing for bezier tesselation.
// See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt.
// "Adaptive forward differencing for rendering curves and surfaces." // "Adaptive forward differencing for rendering curves and surfaces."
@ -4016,6 +4316,8 @@ void nvg__flattenPaths (NVGContext ctx) nothrow @trusted @nogc {
const p = &ctx.commands[i+5]; const p = &ctx.commands[i+5];
if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { if (ctx.tesselatortype == NVGTesselation.DeCasteljau) {
nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
} else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) {
nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner);
} else { } else {
nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner);
} }
@ -5214,6 +5516,7 @@ public void fill (NVGContext ctx) nothrow @trusted @nogc {
// apply global alpha // apply global alpha
NVGPaint fillPaint = state.fill; NVGPaint fillPaint = state.fill;
fillPaint.innerColor.a *= state.alpha; fillPaint.innerColor.a *= state.alpha;
fillPaint.middleColor.a *= state.alpha;
fillPaint.outerColor.a *= state.alpha; fillPaint.outerColor.a *= state.alpha;
ctx.appendCurrentPathToCache(ctx.recset, state.fill); ctx.appendCurrentPathToCache(ctx.recset, state.fill);
@ -5247,10 +5550,12 @@ public void stroke (NVGContext ctx) nothrow @trusted @nogc {
NVGPaint strokePaint = state.stroke; NVGPaint strokePaint = state.stroke;
strokePaint.innerColor.a *= cache.strokeAlphaMul; strokePaint.innerColor.a *= cache.strokeAlphaMul;
strokePaint.middleColor.a *= cache.strokeAlphaMul;
strokePaint.outerColor.a *= cache.strokeAlphaMul; strokePaint.outerColor.a *= cache.strokeAlphaMul;
// apply global alpha // apply global alpha
strokePaint.innerColor.a *= state.alpha; strokePaint.innerColor.a *= state.alpha;
strokePaint.middleColor.a *= state.alpha;
strokePaint.outerColor.a *= state.alpha; strokePaint.outerColor.a *= state.alpha;
ctx.appendCurrentPathToCache(ctx.recset, state.stroke); ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
@ -5289,6 +5594,7 @@ public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothr
// apply global alpha // apply global alpha
NVGPaint fillPaint = state.fill; NVGPaint fillPaint = state.fill;
fillPaint.innerColor.a *= state.alpha; fillPaint.innerColor.a *= state.alpha;
fillPaint.middleColor.a *= state.alpha;
fillPaint.outerColor.a *= state.alpha; fillPaint.outerColor.a *= state.alpha;
//ctx.appendCurrentPathToCache(ctx.recset, state.fill); //ctx.appendCurrentPathToCache(ctx.recset, state.fill);
@ -5331,10 +5637,12 @@ public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union)
NVGPaint strokePaint = state.stroke; NVGPaint strokePaint = state.stroke;
strokePaint.innerColor.a *= cache.strokeAlphaMul; strokePaint.innerColor.a *= cache.strokeAlphaMul;
strokePaint.middleColor.a *= cache.strokeAlphaMul;
strokePaint.outerColor.a *= cache.strokeAlphaMul; strokePaint.outerColor.a *= cache.strokeAlphaMul;
// apply global alpha // apply global alpha
strokePaint.innerColor.a *= state.alpha; strokePaint.innerColor.a *= state.alpha;
strokePaint.middleColor.a *= state.alpha;
strokePaint.outerColor.a *= state.alpha; strokePaint.outerColor.a *= state.alpha;
//ctx.appendCurrentPathToCache(ctx.recset, state.stroke); //ctx.appendCurrentPathToCache(ctx.recset, state.stroke);
@ -7375,6 +7683,7 @@ void nvg__renderText (NVGContext ctx, NVGvertex* verts, int nverts) nothrow @tru
// Apply global alpha // Apply global alpha
paint.innerColor.a *= state.alpha; paint.innerColor.a *= state.alpha;
paint.middleColor.a *= state.alpha;
paint.outerColor.a *= state.alpha; paint.outerColor.a *= state.alpha;
ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts);
@ -11271,7 +11580,7 @@ struct GLNVGpath {
align(1) struct GLNVGfragUniforms { align(1) struct GLNVGfragUniforms {
align(1): align(1):
enum UNIFORM_ARRAY_SIZE = 12; enum UNIFORM_ARRAY_SIZE = 13;
// note: after modifying layout or size of uniform array, // note: after modifying layout or size of uniform array,
// don't forget to also update the fragment shader source! // don't forget to also update the fragment shader source!
align(1) union { align(1) union {
@ -11281,6 +11590,7 @@ align(1):
float[12] scissorMat; // matrices are actually 3 vec4s float[12] scissorMat; // matrices are actually 3 vec4s
float[12] paintMat; float[12] paintMat;
NVGColor innerCol; NVGColor innerCol;
NVGColor middleCol;
NVGColor outerCol; NVGColor outerCol;
float[2] scissorExt; float[2] scissorExt;
float[2] scissorScale; float[2] scissorScale;
@ -11292,7 +11602,8 @@ align(1):
float texType; float texType;
float type; float type;
float doclip; float doclip;
float unused1, unused2, unused3; float midp; // for gradients
float unused2, unused3;
} }
float[4][UNIFORM_ARRAY_SIZE] uniformArray; float[4][UNIFORM_ARRAY_SIZE] uniformArray;
} }
@ -11889,17 +12200,19 @@ bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
#define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)
#define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)
#define innerCol frag[6] #define innerCol frag[6]
#define outerCol frag[7] #define middleCol frag[7]
#define scissorExt frag[8].xy #define outerCol frag[7+1]
#define scissorScale frag[8].zw #define scissorExt frag[8+1].xy
#define extent frag[9].xy #define scissorScale frag[8+1].zw
#define radius frag[9].z #define extent frag[9+1].xy
#define feather frag[9].w #define radius frag[9+1].z
#define strokeMult frag[10].x #define feather frag[9+1].w
#define strokeThr frag[10].y #define strokeMult frag[10+1].x
#define texType int(frag[10].z) #define strokeThr frag[10+1].y
#define type int(frag[10].w) #define texType int(frag[10+1].z)
#define doclip int(frag[11].x) #define type int(frag[10+1].w)
#define doclip int(frag[11+1].x)
#define midp frag[11+1].y
float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { float sdroundrect (in vec2 pt, in vec2 ext, in float rad) {
vec2 ext2 = ext-vec2(rad, rad); vec2 ext2 = ext-vec2(rad, rad);
@ -11946,7 +12259,16 @@ bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc {
// Calculate gradient color using box gradient // Calculate gradient color using box gradient
vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; vec2 pt = (paintMat*vec3(fpos, 1.0)).xy;
float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0);
if (midp <= 0) {
color = mix(innerCol, outerCol, d); color = mix(innerCol, outerCol, d);
} else {
midp = min(midp, 1.0);
if (d < midp) {
color = mix(innerCol, middleCol, d/midp);
} else {
color = mix(middleCol, outerCol, (d-midp)/midp);
}
}
// Combine alpha // Combine alpha
color *= strokeAlpha*scissor; color *= strokeAlpha*scissor;
} else if (type == 2) { /* NSVG_SHADER_FILLIMG */ } else if (type == 2) { /* NSVG_SHADER_FILLIMG */
@ -12177,7 +12499,9 @@ bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* p
memset(frag, 0, (*frag).sizeof); memset(frag, 0, (*frag).sizeof);
frag.innerCol = glnvg__premulColor(paint.innerColor); frag.innerCol = glnvg__premulColor(paint.innerColor);
frag.middleCol = glnvg__premulColor(paint.middleColor);
frag.outerCol = glnvg__premulColor(paint.outerColor); frag.outerCol = glnvg__premulColor(paint.outerColor);
frag.midp = paint.midp;
if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) {
memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof);