From 12f4c4a4adc192be3ab9ab5cdd9ef46898130730 Mon Sep 17 00:00:00 2001 From: ketmar Date: Sat, 27 Nov 2021 11:01:19 +0000 Subject: [PATCH] egra: added some useful primitives to agg drawer FossilOrigin-Name: e1d3c9f339d7917865c2ab93ed3d417d385c719e3b7b4fdb690445a6239a755d --- egra/gfx/aggmini.d | 1123 +++++++++++++++++++++++++++++++--------------------- egra/gfx/base.d | 252 ++++++++++++ egra/test.d | 23 +- 3 files changed, 948 insertions(+), 450 deletions(-) diff --git a/egra/gfx/aggmini.d b/egra/gfx/aggmini.d index e0d343c..abb4842 100644 --- a/egra/gfx/aggmini.d +++ b/egra/gfx/aggmini.d @@ -196,8 +196,8 @@ public: } } - @property float curX () const pure { pragma(inline, true); return cast(float)mCurX/cast(float)PolyBaseSize; } - @property float curY () const pure { pragma(inline, true); return cast(float)mCurY/cast(float)PolyBaseSize; } + @property float currX () const pure { pragma(inline, true); return cast(float)mCurX/cast(float)PolyBaseSize; } + @property float currY () const pure { pragma(inline, true); return cast(float)mCurY/cast(float)PolyBaseSize; } @property int minX () const pure { pragma(inline, true); return mMinX; } @property int minY () const pure { pragma(inline, true); return mMinY; } @@ -839,8 +839,8 @@ public: mGamma[] = g[0..256]; } - @property float curX () const pure { pragma(inline, true); return mOutline.curX; } - @property float curY () const pure { pragma(inline, true); return mOutline.curY; } + @property float currX () const pure { pragma(inline, true); return mOutline.currX; } + @property float currY () const pure { pragma(inline, true); return mOutline.currY; } void moveToFixed (int x, int y) { if ((x>>PolyBaseShift) <= short.min+8 || (x>>PolyBaseShift) >= short.max-8 || @@ -1012,322 +1012,6 @@ public: } -public alias AGGRect = AGGRectImpl!float; - -public struct AGGRectImpl(T) if (__traits(isIntegral, T) || __traits(isFloating, T)) { -public nothrow @trusted @nogc: - alias ValueT = T; - alias Me = AGGRectImpl!T; - -public: - static if (__traits(isFloating, T)) { - T x1, y1, x2, y2; - } else { - T x1=T.min, y1=T.min, x2=T.min, y2=T.min; - } - - this() (T ax1, T ay1, T ax2, T ay2) { - pragma(inline, true); - x1 = ax1; y1 = ay1; x2 = ax2; y2 = ay2; - } - - this() (in auto ref AGGPointImpl!T xy1, in auto ref AGGPointImpl!T xy2) { - pragma(inline, true); - x1 = xy1.x; y1 = xy1.y; x2 = xy2.x; y2 = xy2.y; - } - - void set() (in auto ref AGGPointImpl!T xy1, in auto ref AGGPointImpl!T xy2) { - pragma(inline, true); - x1 = xy1.x; y1 = xy1.y; x2 = xy2.x; y2 = xy2.y; - } - - void set (T ax1, T ay1, T ax2, T ay2) { - pragma(inline, true); - x1 = ax1; y1 = ay1; x2 = ax2; y2 = ay2; - } - - ref Me normalize () { - pragma(inline, true); - if (x1 > x2) { T t = x1; x1 = x2; x2 = t; } - if (y1 > y2) { T t = y1; y1 = y2; y2 = t; } - return this; - } - - bool clip() (in auto ref Me r) { - pragma(inline, true); - if (x2 > r.x2) x2 = r.x2; - if (y2 > r.y2) y2 = r.y2; - if (x1 < r.x1) x1 = r.x1; - if (y1 < r.y1) y1 = r.y1; - return (x1 <= x2 && y1 <= y2); - } - - /* - static if (__traits(isFloating, T)) { - @property bool valid () const pure { pragma(inline, true); import std.math : isFinite; return (x1.isFinite && y1.isFinite && x1.isFinite && y2.isFinite && x1 <= x2 && y1 <= y2); } - } else { - bool valid () const pure { pragma(inline, true); return (x1 != T.min && y1 != T.min && x2 != T.min && y2 != T.min && x1 <= x2 && y1 <= y2); } - } - */ - - bool inside (in T x, in T y) const pure { pragma(inline, true); return (x >= x1 && x <= x2 && y >= y1 && y <= y2); } - bool inside() (in auto ref AGGPointImpl!T p) const pure { pragma(inline, true); return (p.x >= x1 && p.x <= x2 && p.y >= y1 && p.y <= y2); } - - bool overlaps() (in auto ref Me r) const pure { pragma(inline, true); return !(r.x1 > x2 || r.x2 < x1 || r.y1 > y2 || r.y2 < y1); } - - Me intersect() (in auto ref Me r2) const pure { - pragma(inline, true); - AGGRect r = this; - if (r.x2 > r2.x2) r.x2 = r2.x2; - if (r.y2 > r2.y2) r.y2 = r2.y2; - if (r.x1 < r2.x1) r.x1 = r2.x1; - if (r.y1 < r2.y1) r.y1 = r2.y1; - return r; - } - - Me join() (in auto ref Me r2) const pure { - pragma(inline, true); - AGGRect r = this; - if (r.x2 < r2.x2) r.x2 = r2.x2; - if (r.y2 < r2.y2) r.y2 = r2.y2; - if (r.x1 > r2.x1) r.x1 = r2.x1; - if (r.y1 > r2.y1) r.y1 = r2.y1; - return r; - } -} - - -// ////////////////////////////////////////////////////////////////////////// // -public enum AGGClipFlag : uint { - ClippedX1 = 4, - ClippedX2 = 1, - ClippedY1 = 8, - ClippedY2 = 2, - ClippedX = ClippedX1|ClippedX2, - ClippedY = ClippedY1|ClippedY2, -} - - -// Determine the clipping code of the vertex according to the -// Cyrus-Beck line clipping algorithm -// -// | | -// 0110 | 0010 | 0011 -// | | -// -------+--------+-------- clipbox.y2 -// | | -// 0100 | 0000 | 0001 -// | | -// -------+--------+-------- clipbox.y1 -// | | -// 1100 | 1000 | 1001 -// | | -// clipbox.x1 clipbox.x2 -// -// -public uint clippingFlags(T) (T x, T y, in auto ref AGGRectImpl!T clipbox) pure nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - pragma(inline, true); - return - cast(uint)(x > clipbox.x2)| - (cast(uint)(y > clipbox.y2)<<1)| - (cast(uint)(x < clipbox.x1)<<2)| - (cast(uint)(y < clipbox.y1)<<3); -} - -public uint clippingFlags(T) (AGGPointImpl!T P, in auto ref AGGRectImpl!T clipbox) pure nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - pragma(inline, true); - return - cast(uint)(p.x > clipbox.x2)| - (cast(uint)(p.y > clipbox.y2)<<1)| - (cast(uint)(p.x < clipbox.x1)<<2)| - (cast(uint)(p.y < clipbox.y1)<<3); -} - -public uint clippingFlagsX(T) (T x, in auto ref AGGRectImpl!T clipbox) pure nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - pragma(inline, true); - return cast(uint)(x > clipbox.x2)|(cast(uint)(x < clipbox.x1)<<2); -} - -public uint clippingFlagsY(T) (T y, in auto ref AGGRectImpl!T clipbox) pure nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - pragma(inline, true); - return (cast(uint)(y > clipbox.y2)<<1)|(cast(uint)(y < clipbox.y1)<<3); -} - - -// returs clip points in res, and number of clip points as a result (this number can be greater than res.length!) -public uint clipLiangBarsky(T) (in auto ref AGGPointImpl!T p1, in auto ref AGGPointImpl!T p2, in auto ref AGGRectImpl!T clipbox, AGGPointImpl!T[] res) nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - return clipLiangBarsky!T(p1.x, p1.y, p2.x, p2.y, clipbox, res); -} - -public uint clipLiangBarsky(T) (T x1, T y1, T x2, T y2, in auto ref AGGRectImpl!T clipbox, AGGPointImpl!T[] res) nothrow @trusted @nogc if (__traits(isIntegral, T) || __traits(isFloating, T)) { - enum nearzero = cast(float)1e-30; - - float deltax = x2-x1; - float deltay = y2-y1; - - if (deltax == 0) deltax = (x1 > clipbox.x1 ? -nearzero : nearzero); // bump off of the vertical - if (deltay == 0) deltay = (y1 > clipbox.y1 ? -nearzero : nearzero); // bump off of the horizontal - - float xin = void; - float xout = void; - - if (deltax > 0) { - // points to right - xin = clipbox.x1; - xout = clipbox.x2; - } else { - // points to left - xin = clipbox.x2; - xout = clipbox.x1; - } - - float yin = void; - float yout = void; - - if (deltay > 0) { - // points up - yin = clipbox.y1; - yout = clipbox.y2; - } else { - yin = clipbox.y2; - yout = clipbox.y1; - } - - immutable float tinx = (xin-x1)/deltax; - immutable float tiny = (yin-y1)/deltay; - - float tin1 = void; - float tin2 = void; - - if (tinx < tiny) { - // hits x first - tin1 = tinx; - tin2 = tiny; - } else { - // hits y first - tin1 = tiny; - tin2 = tinx; - } - - uint np = 0; - - void putClip (in T x, in T y) nothrow @trusted @nogc { - if (np < res.length) res.ptr[np] = AGGPointImpl!T(x, y); - ++np; - } - - if (tin1 <= 1) { - if (0 < tin1) putClip(cast(T)xin, cast(T)yin); - - if (tin2 <= 1) { - immutable float toutx = (xout-x1)/deltax; - immutable float touty = (yout-y1)/deltay; - - immutable float tout1 = (toutx < touty ? toutx : touty); - - if (tin2 > 0 || tout1 > 0) { - if (tin2 <= tout1) { - if (tin2 > 0) { - if (tinx > tiny) { - putClip(cast(T)xin, cast(T)(y1+tinx*deltay)); - } else { - putClip(cast(T)(x1+tiny*deltax), cast(T)yin); - } - } - - if (tout1 < 1) { - if (toutx < touty) { - putClip(cast(T)xout, cast(T)(y1+toutx*deltay)); - } else { - putClip(cast(T)(x1+touty*deltax), cast(T)yout); - } - } else { - putClip(x2, y2); - } - } else { - if (tinx > tiny) { - putClip(cast(T)xin, cast(T)yout); - } else { - putClip(cast(T)xout, cast(T)yin); - } - } - } - } - } - - return np; -} - - -// returns invalid point if cannot move it -/+ -AGGPointImpl!T clip_move_point(T) (T x1, T y1, T x2, T y2, in auto ref AGGRectImpl!T clipbox, uint flags) nothrow @trusted @nogc { - AGGPointImpl!T res; - - if (flags&AGGClipFlag.ClippedX) { - if (x1 == x2) return false; - T bound = (flags&AGGClipFlag.ClippedX1 ? clipbox.x1 : clipbox.x2); - res.y = cast(T)(cast(double)(bound-x1)*(y2-y1)/(x2-x1)+y1); - res.x = bound; - } - - flags = clippingFlagsY(*y, clipbox); - if (flags&AGGClipFlag.ClippedY) { - if (y1 == y2) return false; - T bound = (flags & AGGClipFlag.ClippedY1 ? clipbox.y1 : clipbox.y2); - *x = cast(T)(cast(double)(bound - y1) * (x2 - x1) / (y2 - y1) + x1); - *y = bound; - } - return true; -} -+/ - - -// Returns: ret >= 4 - Fully clipped -// (ret & 1) != 0 - First point has been moved -// (ret & 2) != 0 - Second point has been moved -// -/+ -uint clip_line_segment(T) (T* x1, T* y1, T* x2, T* y2, in ref AGGRectImpl!T clipbox) nothrow @trusted @nogc { - uint f1 = clipping_flags(*x1, *y1, clipbox); - uint f2 = clipping_flags(*x2, *y2, clipbox); - uint ret = 0; - - if ((f2 | f1) == 0) return 0; // Fully visible - - if ((f1 & AGGClipFlag.ClippedX) != 0 && - (f1 & AGGClipFlag.ClippedX) == (f2 & AGGClipFlag.ClippedX)) - { - // Fully clipped - return 4; - } - - if ((f1 & AGGClipFlag.ClippedY) != 0 && - (f1 & AGGClipFlag.ClippedY) == (f2 & AGGClipFlag.ClippedY)) - { - // Fully clipped - return 4; - } - - T tx1 = *x1; - T ty1 = *y1; - T tx2 = *x2; - T ty2 = *y2; - if (f1) { - if (!clip_move_point(tx1, ty1, tx2, ty2, clipbox, x1, y1, f1)) return 4; - if (*x1 == *x2 && *y1 == *y2) return 4; - ret |= 1; - } - if (f2) { - if (!clip_move_point(tx1, ty1, tx2, ty2, clipbox, x2, y2, f2)) return 4; - if (*x1 == *x2 && *y1 == *y2) return 4; - ret |= 2; - } - return ret; -} -+/ - - // ////////////////////////////////////////////////////////////////////////// // public enum FillRule { NonZero, @@ -1377,11 +1061,48 @@ protected: nothrow @trusted @nogc: protected: - void addVertexIntr (in float fx, in float fy, bool asMove=false) { + enum distTol = cast(float)(1.0f/256.0f); + enum KAPPA90 = 0.5522847493f; // length proportional to radius of a cubic bezier handle for 90deg arcs + + static bool ptEquals (in float x0, in float y0, in float x1, in float y1) pure { enum EPS = cast(float)(1.0f/256.0f); + static bool eq (in float a, in float b) pure nothrow @safe @nogc { immutable float df = a-b; return (df < 0.0f ? (-df < EPS) : (df < EPS)); } + return (eq(x0, x1) && eq(y0, y1)); + } + + static float distPtSeg (in float x, in float y, in float px, in float py, in float qx, in float qy) pure nothrow @safe @nogc { + immutable float pqx = qx-px; + immutable float pqy = qy-py; + float dx = x-px; + float dy = y-py; + immutable float d = pqx*pqx+pqy*pqy; + float t = pqx*dx+pqy*dy; + if (d > 0) t /= d; + if (t < 0) t = 0; else if (t > 1) t = 1; + dx = px+t*pqx-x; + dy = py+t*pqy-y; + return dx*dx+dy*dy; + } + + static float cross() (in float dx0, in float dy0, in float dx1, in float dy1) pure { pragma(inline, true); return (dx1*dy0-dx0*dy1); } + static T min(T) (in T a, in T b) pure { pragma(inline, true); return (a < b ? a : b); } + static T max(T) (in T a, in T b) pure { pragma(inline, true); return (a > b ? a : b); } + static T sign(T) (in T a) pure { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } + + static float normalize (float* x, float* y) nothrow @safe @nogc { + import core.stdc.math : sqrtf; + float d = sqrtf((*x)*(*x)+(*y)*(*y)); + if (d > 1e-6f) { + immutable float id = 1.0f/d; + *x *= id; + *y *= id; + } + return d; + } + + void addVertexIntr (in float fx, in float fy, bool asMove=false) { //static bool eq (in float a, in float b) pure nothrow @safe @nogc { import std.math : abs; return (abs(a-b) < EPS); } - static bool eq (in float a, in float b) pure nothrow @safe @nogc { immutable float df = a-b; return (df < 0 ? (-df < EPS) : (df < EPS)); } - if (vtxcount > 0 && eq(fx, vtx[vtxcount-1].x) && eq(fy, vtx[vtxcount-1].y)) { + if (vtxcount > 0 && ptEquals(fx, fy, vtx[vtxcount-1].x, vtx[vtxcount-1].y)) { if (asMove != vtx[vtxcount-1].asmove) return; } if (vtxcount+1 > vtxsize) { @@ -1430,6 +1151,343 @@ public: @property FillRule fillRule () const pure { pragma(inline, true); return rast.fillRule; } @property void fillRule (FillRule v) { pragma(inline, true); rast.fillRule = v; } + @property float currX () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].x : 0.0f); } + @property float currY () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].y : 0.0f); } + + // ////////////////////////////////////////////////////////////////////////// // + ref AGGDrawer beginFrame () { + vtxcount = 0; + //addVertex(0, 0, true); + mStroker.width = 1.5f; + rast.reset(); + return this; + } + + ref AGGDrawer cancelFrame () { + vtxcount = 0; + //addVertex(0, 0, true); + rast.reset(); + return this; + } + + ref AGGDrawer endFrame (in GxColor c) { + GxRect clipRect = gxClipRect; + if (clipRect.intersect(0, 0, VBufWidth, VBufHeight)) { + rast.render(clipRect, c); + } + return beginFrame(); + } + + ref AGGDrawer beginPath () { + pragma(inline, true); + vtxcount = 0; + return this; + } + + ref AGGDrawer closePath () { + if (vtxcount > 1 && !vtx[vtxcount-1].asmove) { + uint vp = vtxcount; + while (vp > 0 && !vtx[vp-1].asmove) --vp; + addVertex(vtx[vp-1].x, vtx[vp-1].y); // close it + } + return this; + } + + // starts new sub-path with specified point as first point + ref AGGDrawer moveTo (in float x, in float y) { pragma(inline, true); addVertex(x, y, true); return this; } + + // adds line segment from the last point in the path to the specified point + ref AGGDrawer lineTo (in float x, in float y) { pragma(inline, true); addVertex(x, y); return this; } + + // adds cubic bezier segment from last point in the path via two control points to the specified point + ref AGGDrawer bezierTo (in float x2, in float y2, in float x3, in float y3, in float x4, in float y4) { + pragma(inline, true); + //tesselateBezierMcSeem(currX, currY, x2, y2, x3, y3, x4, y4, 0); + tesselateBezier(currX, currY, x2, y2, x3, y3, x4, y4); + return this; + } + + // adds quadratic bezier segment from last point in the path via a control point to the specified point + void quadTo (in float cx, in float cy, in float x, in float y) { + immutable float x0 = currX; + immutable float y0 = currY; + bezierTo( + x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), + x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), + x, y, + ); + } + + // adds an arc segment at the corner defined by the last path point, and two specified points + ref AGGDrawer arcTo (in float x1, in float y1, in float x2, in float y2, in float radius) { + import core.stdc.math : acosf, tanf, atan2f; + + immutable float x0 = currX; + immutable float y0 = currY; + + // handle degenerate cases + if (ptEquals(x0, y0, x1, y1) || + ptEquals(x1, y1, x2, y2) || + distPtSeg(x1, y1, x0, y0, x2, y2) < distTol*distTol || + radius < distTol) + { + lineTo(x1, y1); + return this; + } + + // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) + float dx0 = x0-x1; + float dy0 = y0-y1; + float dx1 = x2-x1; + float dy1 = y2-y1; + normalize(&dx0, &dy0); + normalize(&dx1, &dy1); + immutable float a = acosf(dx0*dx1+dy0*dy1); + immutable float d = radius/tanf(a/2.0f); + + if (d > 10000.0f) { + lineTo(x1, y1); + return this; + } + + float cx = void, cy = void, a0 = void, a1 = void; + Winding dir = void; + if (cross(dx0, dy0, dx1, dy1) > 0.0f) { + cx = x1+dx0*d+dy0*radius; + cy = y1+dy0*d+-dx0*radius; + a0 = atan2f(dx0, -dy0); + a1 = atan2f(-dx1, dy1); + dir = Winding.CW; + //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } else { + cx = x1+dx0*d+-dy0*radius; + cy = y1+dy0*d+dx0*radius; + a0 = atan2f(-dx0, dy0); + a1 = atan2f(dx1, -dy1); + dir = Winding.CCW; + //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } + + return arc(dir, cx, cy, radius, a0, a1); // first is line + } + + + // ////////////////////////////////////////////////////////////////////////// // + // fill pathes + void fill () { + //pragma(inline, true); + foreach (const ref Vertex vt; vtx[0..vtxcount]) { + if (vt.asmove) rast.moveTo(vt.x, vt.y); else rast.lineTo(vt.x, vt.y); + } + } + + // stroke pathes + void stroke () { + //if (mStroker.width <= 1.0) { contour(); return; } + mStroker.removeAll(); + + bool waitingLine = true; + foreach (const ref Vertex vt; vtx[0..vtxcount]) { + mStroker.addVertex(vt.x, vt.y, (vt.asmove ? PathCommand.MoveTo : PathCommand.LineTo)); + } + mStroker.addVertex(0, 0, PathCommand.Stop); + + float x, y; + mStroker.rewind(); + for (;;) { + auto cmd = mStroker.vertex(&x, &y); + if (isStop(cmd)) break; + if (isMoveTo(cmd)) { rast.moveTo(x, y); continue; } + if (isLineTo(cmd)) { rast.lineTo(x, y); continue; } + if (isEndPoly(cmd)) { + /* + if (is_close(cmd) && !first) { + //writeln("s=(", sx, ",", sy, "); c=(", x, ",", y, ")"); + pts.add(PathPoint(sx, sy, false)); + } + */ + } + } + } + + // contour pathes + void contour () { + Contourer ctr; + ctr.removeAll(); + + ctr.lineCap = mStroker.lineCap; + ctr.lineJoin = mStroker.lineJoin; + ctr.innerJoin = mStroker.innerJoin; + + ctr.width = mStroker.width; + ctr.miterLimit = mStroker.miterLimit; + ctr.innerMiterLimit = mStroker.innerMiterLimit; + ctr.approximationScale = mStroker.approximationScale; + + bool waitingLine = true; + foreach (const ref Vertex vt; vtx[0..vtxcount]) { + ctr.addVertex(vt.x, vt.y, (vt.asmove ? PathCommand.MoveTo : PathCommand.LineTo)); + } + ctr.addVertex(0, 0, PathCommand.Stop); + + float x, y; + ctr.rewind(); + for (;;) { + auto cmd = ctr.vertex(&x, &y); + if (isStop(cmd)) break; + if (isMoveTo(cmd)) { rast.moveTo(x, y); continue; } + if (isLineTo(cmd)) { rast.lineTo(x, y); continue; } + } + } + + + // ////////////////////////////////////////////////////////////////////////// // + enum Winding { CW, CCW } + + /* Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, + * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). + * Angles are specified in radians. + * + * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo + */ + ref AGGDrawer arc(string mode="original") (Winding dir, in float cx, in float cy, in float r, + in float a0, in float a1) + { + static assert(mode == "original" || mode == "move" || mode == "line"); + import core.stdc.math : fabsf, cosf, sinf; + + //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); + static if (mode == "original") { + bool asMove = (vtxcount == 0); + } else static if (mode == "move") { + enum asMove = true; + } else static if (mode == "line") { + enum asMove = false; + } else { + static assert(0, "wtf?!"); + } + + // clamp angles + float da = a1-a0; + if (dir == Winding.CW) { + if (fabsf(da) >= FLT_PI*2.0f) { + da = FLT_PI*2.0f; + } else { + while (da < 0.0f) da += FLT_PI*2.0f; + } + } else { + if (fabsf(da) >= FLT_PI*2.0f) { + da = -FLT_PI*2; + } else { + while (da > 0.0f) da -= FLT_PI*2.0f; + } + } + + // Split arc into max 90 degree segments. + immutable int ndivs = max(1, min(cast(int)(fabsf(da)/(FLT_PI*0.5f)+0.5f), 5)); + immutable float hda = (da/cast(float)ndivs)/2.0f; + float kappa = fabsf(4.0f/3.0f*(1.0f-cosf(hda))/sinf(hda)); + if (dir == Winding.CCW) kappa = -kappa; + + int nvals = 0; + float px = 0, py = 0, ptanx = 0, ptany = 0; + foreach (int i; 0..ndivs+1) { + immutable float a = a0+da*(i/cast(float)ndivs); + immutable float dx = cosf(a); + immutable float dy = sinf(a); + immutable float x = cx+dx*r; + immutable float y = cy+dy*r; + immutable float tanx = -dy*r*kappa; + immutable float tany = dx*r*kappa; + + if (i == 0) { + addVertex(x, y, asMove); + } else { + bezierTo(px+ptanx, py+ptany, x-tanx, y-tany, x, y); + } + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + return this; + } + + // creates new rectangle shaped sub-path + ref AGGDrawer rect (in float x, in float y, in float w, in float h) { + if (w && h) { + moveTo(x, y); + lineTo(x, y+h); + lineTo(x+w, y+h); + lineTo(x+w, y); + lineTo(x, y); // close it + } + return this; + } + + // creates new rounded rectangle shaped sub-path + ref AGGDrawer roundedRect (in float x, in float y, in float w, in float h, in float radius) { + return roundedRectVarying(x, y, w, h, radius, radius, radius, radius); + } + + // creates new rounded rectangle shaped sub-path + // specify ellipse width and height to round corners according to it + ref AGGDrawer roundedRectEllipse (in float x, in float y, in float w, in float h, in float rw, in float rh) { + if (rw < 0.1f || rh < 0.1f) return rect(x, y, w, h); + moveTo(x+rw, y); + lineTo(x+w-rw, y); + bezierTo(x+w-rw*(1.0f-KAPPA90), y, x+w, y+rh*(1.0f-KAPPA90), x+w, y+rh); + lineTo(x+w, y+h-rh); + bezierTo(x+w, y+h-rh*(1.0f-KAPPA90), x+w-rw*(1.0f-KAPPA90), y+h, x+w-rw, y+h); + lineTo(x+rw, y+h); + bezierTo(x+rw*(1.0f-KAPPA90), y+h, x, y+h-rh*(1.0f-KAPPA90), x, y+h-rh); + lineTo(x, y+rh); + bezierTo(x, y+rh*(1.0f-KAPPA90), x+rw*(1.0f-KAPPA90), y, x+rw, y); + return closePath(); + } + + // creates new rounded rectangle shaped sub-path + // this one allows you to specify different rounding radii for each corner + ref AGGDrawer roundedRectVarying (in float x, in float y, in float w, in float h, + in float radTopLeft, in float radTopRight, + in float radBottomRight, in float radBottomLeft) + { + import core.stdc.math : fabsf; + if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) return rect(x, y, w, h); + immutable float halfw = fabsf(w)*0.5f; + immutable float halfh = fabsf(h)*0.5f; + immutable float rxBL = min(radBottomLeft, halfw)*sign(w), ryBL = min(radBottomLeft, halfh)*sign(h); + immutable float rxBR = min(radBottomRight, halfw)*sign(w), ryBR = min(radBottomRight, halfh)*sign(h); + immutable float rxTR = min(radTopRight, halfw)*sign(w), ryTR = min(radTopRight, halfh)*sign(h); + immutable float rxTL = min(radTopLeft, halfw)*sign(w), ryTL = min(radTopLeft, halfh)*sign(h); + moveTo(x, y+ryTL); + lineTo(x, y+h-ryBL); + bezierTo(x, y+h-ryBL*(1.0f-KAPPA90), x+rxBL*(1.0f-KAPPA90), y+h, x+rxBL, y+h); + lineTo(x+w-rxBR, y+h); + bezierTo(x+w-rxBR*(1.0f-KAPPA90), y+h, x+w, y+h-ryBR*(1.0f-KAPPA90), x+w, y+h-ryBR); + lineTo(x+w, y+ryTR); + bezierTo(x+w, y+ryTR*(1.0f-KAPPA90), x+w-rxTR*(1.0f-KAPPA90), y, x+w-rxTR, y); + lineTo(x+rxTL, y); + bezierTo(x+rxTL*(1.0f-KAPPA90), y, x, y+ryTL*(1.0f-KAPPA90), x, y+ryTL); + return closePath(); + } + + // creates new ellipse shaped sub-path + ref AGGDrawer ellipse (in float cx, in float cy, in float rx, in float ry) { + moveTo(cx-rx, cy); + bezierTo(cx-rx, cy+ry*KAPPA90, cx-rx*KAPPA90, cy+ry, cx, cy+ry); + bezierTo(cx+rx*KAPPA90, cy+ry, cx+rx, cy+ry*KAPPA90, cx+rx, cy); + bezierTo(cx+rx, cy-ry*KAPPA90, cx+rx*KAPPA90, cy-ry, cx, cy-ry); + bezierTo(cx-rx*KAPPA90, cy-ry, cx-rx, cy-ry*KAPPA90, cx-rx, cy); + return closePath(); + } + + // creates new circle shaped sub-path + ref AGGDrawer circle (in float cx, in float cy, in float r) { + return ellipse(cx, cy, r, r); + } + private: // ////////////////////////////////////////////////////////////////////////// // void tesselateBezierDCj (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*/) { @@ -1776,126 +1834,9 @@ private: } } - -public: - // ////////////////////////////////////////////////////////////////////////// // - @property float curX () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].x : 0.0f); } - @property float curY () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].y : 0.0f); } - - void beginFrame () { - vtxcount = 0; - //addVertex(0, 0, true); - mStroker.width = 1.5f; - rast.reset(); - } - - void cancelFrame () { - vtxcount = 0; - //addVertex(0, 0, true); - rast.reset(); - } - - void endFrame (in GxColor c) { - GxRect clipRect = gxClipRect; - if (!clipRect.intersect(0, 0, VBufWidth, VBufHeight)) return; - rast.render(clipRect, c); - vtxcount = 0; - //addVertex(0, 0, true); - rast.reset(); - } - - void beginPath () { - pragma(inline, true); - vtxcount = 0; - } - - void moveTo (in float x, in float y) { pragma(inline, true); addVertex(x, y, true); } - void lineTo (in float x, in float y) { pragma(inline, true); addVertex(x, y); } - - void bezierTo (in float x2, in float y2, in float x3, in float y3, in float x4, in float y4) { - pragma(inline, true); - //tesselateBezierMcSeem(curX, curY, x2, y2, x3, y3, x4, y4, 0); - tesselateBezier(curX, curY, x2, y2, x3, y3, x4, y4); - } - - void quadTo (in float cx, in float cy, in float x, in float y) { - immutable float x0 = curX; - immutable float y0 = curY; - bezierTo( - x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), - x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), - x, y, - ); - } - - void fill () { - //pragma(inline, true); - foreach (const ref Vertex vt; vtx[0..vtxcount]) { - if (vt.asmove) rast.moveTo(vt.x, vt.y); else rast.lineTo(vt.x, vt.y); - } - } - - void stroke () { - //if (mStroker.width <= 1.0) { contour(); return; } - mStroker.removeAll(); - - bool waitingLine = true; - foreach (const ref Vertex vt; vtx[0..vtxcount]) { - mStroker.addVertex(vt.x, vt.y, (vt.asmove ? PathCommand.MoveTo : PathCommand.LineTo)); - } - mStroker.addVertex(0, 0, PathCommand.Stop); - - float x, y; - mStroker.rewind(); - for (;;) { - auto cmd = mStroker.vertex(&x, &y); - if (isStop(cmd)) break; - if (isMoveTo(cmd)) { rast.moveTo(x, y); continue; } - if (isLineTo(cmd)) { rast.lineTo(x, y); continue; } - if (isEndPoly(cmd)) { - /* - if (is_close(cmd) && !first) { - //writeln("s=(", sx, ",", sy, "); c=(", x, ",", y, ")"); - pts.add(PathPoint(sx, sy, false)); - } - */ - } - } - } - - void contour () { - Contourer ctr; - ctr.removeAll(); - - ctr.lineCap = mStroker.lineCap; - ctr.lineJoin = mStroker.lineJoin; - ctr.innerJoin = mStroker.innerJoin; - - ctr.width = mStroker.width; - ctr.miterLimit = mStroker.miterLimit; - ctr.innerMiterLimit = mStroker.innerMiterLimit; - ctr.approximationScale = mStroker.approximationScale; - - bool waitingLine = true; - foreach (const ref Vertex vt; vtx[0..vtxcount]) { - ctr.addVertex(vt.x, vt.y, (vt.asmove ? PathCommand.MoveTo : PathCommand.LineTo)); - } - ctr.addVertex(0, 0, PathCommand.Stop); - - float x, y; - ctr.rewind(); - for (;;) { - auto cmd = ctr.vertex(&x, &y); - if (isStop(cmd)) break; - if (isMoveTo(cmd)) { rast.moveTo(x, y); continue; } - if (isLineTo(cmd)) { rast.lineTo(x, y); continue; } - } - } - - // ////////////////////////////////////////////////////////////////////////// // public enum BaphometDims = 512; // [0..511] - void renderBaphomet (float ofsx=0.0f, float ofsy=0.0f, float scalex=1.0f, float scaley=1.0f) { + public void renderBaphomet (float ofsx=0.0f, float ofsy=0.0f, float scalex=1.0f, float scaley=1.0f) { auto path = cast(const(ubyte)[])baphometPath; immutable plen = path.length; uint ppos = 0; @@ -3557,3 +3498,303 @@ private: } } } + + +// ////////////////////////////////////////////////////////////////////////// // +// Matrices and Transformations + +// matrix class +public align(1) struct AGGMatrix { +align(1): +private: + static immutable float[6] IdentityMat = [ + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + ]; + +public: + /// Matrix values. Initial value is identity matrix. + float[6] mat = [ + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + ]; + +public nothrow @trusted @nogc: + /// Create Matrix with the given values. + this (const(float)[] amat...) { + pragma(inline, true); + if (amat.length >= 6) { + mat.ptr[0..6] = amat.ptr[0..6]; + } else { + mat.ptr[0..6] = 0.0f; + mat.ptr[0..amat.length] = amat[]; + } + } + + /// Can be used to check validity of [inverted] result + @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } + + /// Returns `true` if this matrix is identity matrix. + @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } + + /// Returns new inverse matrix. + /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. + AGGMatrix inverted () const { + AGGMatrix res = this; + res.invert; + return res; + } + + /// Inverts this matrix. + /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. + ref AGGMatrix invert () { + float[6] inv = void; + immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; + if (det > -1e-6 && det < 1e-6) { + inv[] = float.nan; + } else { + immutable double invdet = 1.0/det; + inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); + inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); + inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); + inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); + inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); + inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); + } + mat.ptr[0..6] = inv.ptr[0..6]; + return this; + } + + /// Sets this matrix to identity matrix. + ref AGGMatrix identity () { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } + + /// Translate this matrix. + ref AGGMatrix translate (in float tx, in float ty) { + version(aliced) pragma(inline, true); + return this.mul(Translated(tx, ty)); + } + + /// Scale this matrix. + ref AGGMatrix scale (in float sx, in float sy) { + version(aliced) pragma(inline, true); + return this.mul(Scaled(sx, sy)); + } + + /// Rotate this matrix. + ref AGGMatrix rotate (in float a) { + version(aliced) pragma(inline, true); + return this.mul(Rotated(a)); + } + + /// Skew this matrix by X axis. + ref AGGMatrix skewX (in float a) { + version(aliced) pragma(inline, true); + return this.mul(SkewedX(a)); + } + + /// Skew this matrix by Y axis. + ref AGGMatrix skewY (in float a) { + version(aliced) pragma(inline, true); + return this.mul(SkewedY(a)); + } + + /// Skew this matrix by both axes. + ref AGGMatrix skewY (in float ax, in float ay) { + version(aliced) pragma(inline, true); + return this.mul(SkewedXY(ax, ay)); + } + + /// Transform point with this matrix. `null` destinations are allowed. + /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. + void point (float* dx, float* dy, float sx, float sy) { + version(aliced) pragma(inline, true); + if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; + if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; + } + + /// Transform point with this matrix. + void point (ref float x, ref float y) { + version(aliced) pragma(inline, true); + immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; + immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; + x = nx; + y = ny; + } + + /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). + ref AGGMatrix mul() (in auto ref AGGMatrix s) { + immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; + immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; + immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; + mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; + mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; + mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; + mat.ptr[0] = t0; + mat.ptr[2] = t2; + mat.ptr[4] = t4; + return this; + } + + /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). + /// Sets the transform to the result of multiplication of two transforms, of A = B*A. + /// Group: matrices + ref AGGMatrix premul() (in auto ref AGGMatrix s) { + AGGMatrix s2 = s; + s2.mul(this); + mat[] = s2.mat[]; + return this; + } + + /// Multiply this matrix by [s], return result as new matrix. + /// Performs operations in this left-to-right order. + AGGMatrix opBinary(string op="*") (in auto ref AGGMatrix s) const { + version(aliced) pragma(inline, true); + AGGMatrix res = this; + res.mul(s); + return res; + } + + /// Multiply this matrix by [s]. + /// Performs operations in this left-to-right order. + ref AGGMatrix opOpAssign(string op="*") (in auto ref AGGMatrix s) { + version(aliced) pragma(inline, true); + return this.mul(s); + } + + float scaleX () const { pragma(inline, true); import core.stdc.math : sqrtf; return sqrtf(mat.ptr[0]*mat.ptr[0]+mat.ptr[2]*mat.ptr[2]); } /// Returns x scaling of this matrix. + float scaleY () const { pragma(inline, true); import core.stdc.math : sqrtf; return sqrtf(mat.ptr[1]*mat.ptr[1]+mat.ptr[3]*mat.ptr[3]); } /// Returns y scaling of this matrix. + float rotation () const { pragma(inline, true); import core.stdc.math : atan2f; return atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. + float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. + float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. + + ref AGGMatrix scaleX (in float v) { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. + ref AGGMatrix scaleY (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. + ref AGGMatrix rotation (in float v) { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. + ref AGGMatrix tx (in float v) { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. + ref AGGMatrix ty (in float v) { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. + + /// Utility function to be used in `setXXX()`. + /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster + ref AGGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) { + import core.stdc.math : sinf, cosf; + immutable float cs = cosf(a), sn = sinf(a); + mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; + mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; + mat.ptr[4] = tx; mat.ptr[5] = ty; + return this; + } + + /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster + ref AGGMatrix rotateTransform (in float a, in float tx, in float ty) { + import core.stdc.math : sinf, cosf; + immutable float cs = cosf(a), sn = sinf(a); + mat.ptr[0] = cs; mat.ptr[1] = sn; + mat.ptr[2] = -sn; mat.ptr[3] = cs; + mat.ptr[4] = tx; mat.ptr[5] = ty; + return this; + } + + /// Returns new identity matrix. + static AGGMatrix Identity () { pragma(inline, true); return AGGMatrix.init; } + + /// Returns new translation matrix. + static AGGMatrix Translated (in float tx, in float ty) { + version(aliced) pragma(inline, true); + AGGMatrix res = void; + res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; + res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; + res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; + return res; + } + + /// Returns new scaling matrix. + static AGGMatrix Scaled (in float sx, in float sy) { + version(aliced) pragma(inline, true); + AGGMatrix res = void; + res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; + res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; + res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; + return res; + } + + /// Returns new rotation matrix. Angle is specified in radians. + static AGGMatrix Rotated (in float a) { + version(aliced) pragma(inline, true); + import core.stdc.math : sinf, cosf; + immutable float cs = cosf(a), sn = sinf(a); + AGGMatrix res = void; + res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; + res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; + res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; + return res; + } + + /// Returns new x-skewing matrix. Angle is specified in radians. + static AGGMatrix SkewedX (in float a) { + version(aliced) pragma(inline, true); + import core.stdc.math : tanf; + AGGMatrix res = void; + res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; + res.mat.ptr[2] = tanf(a); res.mat.ptr[3] = 1.0f; + res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; + return res; + } + + /// Returns new y-skewing matrix. Angle is specified in radians. + static AGGMatrix SkewedY (in float a) { + version(aliced) pragma(inline, true); + import core.stdc.math : tanf; + AGGMatrix res = void; + res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = tanf(a); + res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; + res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; + return res; + } + + /// Returns new xy-skewing matrix. Angles are specified in radians. + static AGGMatrix SkewedXY (in float ax, in float ay) { + version(aliced) pragma(inline, true); + import core.stdc.math : tanf; + AGGMatrix res = void; + res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = tanf(ay); + res.mat.ptr[2] = tanf(ax); res.mat.ptr[3] = 1.0f; + res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; + return res; + } + + /// Utility function to be used in `setXXX()`. + /// This is the same as doing: `AGGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster + static AGGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { + AGGMatrix res = void; + res.scaleRotateTransform(xscale, yscale, a, tx, ty); + return res; + } + + /// This is the same as doing: `AGGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster + static AGGMatrix RotatedTransformed (in float a, in float tx, in float ty) { + AGGMatrix res = void; + res.rotateTransform(a, tx, ty); + return res; + } + +public: + // premultiplies current coordinate system by specified matrix + ref AGGMatrix transform() (in auto ref AGGMatrix mt) { pragma(inline, true); this *= mt; return this; } + + // translates current coordinate system + ref AGGMatrix translatePre (in float x, in float y) { pragma(inline, true); return premul(AGGMatrix.Translated(x, y)); } + + // rotates current coordinate system; angle is specified in radians + ref AGGMatrix rotatePre (in float angle) { pragma(inline, true); return premul(AGGMatrix.Rotated(angle)); } + + // skews the current coordinate system along X axis; angle is specified in radians + ref AGGMatrix skewXPre (in float angle) { pragma(inline, true); return premul(AGGMatrix.SkewedX(angle)); } + + // skews the current coordinate system along Y axis; angle is specified in radians + ref AGGMatrix skewYPre (in float angle) { pragma(inline, true); return premul(AGGMatrix.SkewedY(angle)); } + + // scales the current coordinate system + ref AGGMatrix scalePre (in float x, in float y) { pragma(inline, true); return premul(AGGMatrix.Scaled(x, y)); } +} diff --git a/egra/gfx/base.d b/egra/gfx/base.d index e92e77b..e283ee6 100644 --- a/egra/gfx/base.d +++ b/egra/gfx/base.d @@ -668,6 +668,258 @@ public enum gxRGBA(int r, int g, int b, int a) = (clampToByte(a)<<24)|(clampToBy // ////////////////////////////////////////////////////////////////////////// // +// namespace +public struct GxColors { + enum k8orange = gxRGB!(255, 127, 0); + + enum aliceblue = gxRGB!(240, 248, 255); + enum antiquewhite = gxRGB!(250, 235, 215); + enum aqua = gxRGB!(0, 255, 255); + enum aquamarine = gxRGB!(127, 255, 212); + enum azure = gxRGB!(240, 255, 255); + enum beige = gxRGB!(245, 245, 220); + enum bisque = gxRGB!(255, 228, 196); + enum black = gxRGB!(0, 0, 0); // basic color + enum blanchedalmond = gxRGB!(255, 235, 205); + enum blue = gxRGB!(0, 0, 255); // basic color + enum blueviolet = gxRGB!(138, 43, 226); + enum brown = gxRGB!(165, 42, 42); + enum burlywood = gxRGB!(222, 184, 135); + enum cadetblue = gxRGB!(95, 158, 160); + enum chartreuse = gxRGB!(127, 255, 0); + enum chocolate = gxRGB!(210, 105, 30); + enum coral = gxRGB!(255, 127, 80); + enum cornflowerblue = gxRGB!(100, 149, 237); + enum cornsilk = gxRGB!(255, 248, 220); + enum crimson = gxRGB!(220, 20, 60); + enum cyan = gxRGB!(0, 255, 255); // basic color + enum darkblue = gxRGB!(0, 0, 139); + enum darkcyan = gxRGB!(0, 139, 139); + enum darkgoldenrod = gxRGB!(184, 134, 11); + enum darkgray = gxRGB!(169, 169, 169); + enum darkgreen = gxRGB!(0, 100, 0); + enum darkgrey = gxRGB!(169, 169, 169); + enum darkkhaki = gxRGB!(189, 183, 107); + enum darkmagenta = gxRGB!(139, 0, 139); + enum darkolivegreen = gxRGB!(85, 107, 47); + enum darkorange = gxRGB!(255, 140, 0); + enum darkorchid = gxRGB!(153, 50, 204); + enum darkred = gxRGB!(139, 0, 0); + enum darksalmon = gxRGB!(233, 150, 122); + enum darkseagreen = gxRGB!(143, 188, 143); + enum darkslateblue = gxRGB!(72, 61, 139); + enum darkslategray = gxRGB!(47, 79, 79); + enum darkslategrey = gxRGB!(47, 79, 79); + enum darkturquoise = gxRGB!(0, 206, 209); + enum darkviolet = gxRGB!(148, 0, 211); + enum deeppink = gxRGB!(255, 20, 147); + enum deepskyblue = gxRGB!(0, 191, 255); + enum dimgray = gxRGB!(105, 105, 105); + enum dimgrey = gxRGB!(105, 105, 105); + enum dodgerblue = gxRGB!(30, 144, 255); + enum firebrick = gxRGB!(178, 34, 34); + enum floralwhite = gxRGB!(255, 250, 240); + enum forestgreen = gxRGB!(34, 139, 34); + enum fuchsia = gxRGB!(255, 0, 255); + enum gainsboro = gxRGB!(220, 220, 220); + enum ghostwhite = gxRGB!(248, 248, 255); + enum gold = gxRGB!(255, 215, 0); + enum goldenrod = gxRGB!(218, 165, 32); + enum gray = gxRGB!(128, 128, 128); // basic color + enum green = gxRGB!(0, 128, 0); // basic color + enum greenyellow = gxRGB!(173, 255, 47); + enum grey = gxRGB!(128, 128, 128); // basic color + enum honeydew = gxRGB!(240, 255, 240); + enum hotpink = gxRGB!(255, 105, 180); + enum indianred = gxRGB!(205, 92, 92); + enum indigo = gxRGB!(75, 0, 130); + enum ivory = gxRGB!(255, 255, 240); + enum khaki = gxRGB!(240, 230, 140); + enum lavender = gxRGB!(230, 230, 250); + enum lavenderblush = gxRGB!(255, 240, 245); + enum lawngreen = gxRGB!(124, 252, 0); + enum lemonchiffon = gxRGB!(255, 250, 205); + enum lightblue = gxRGB!(173, 216, 230); + enum lightcoral = gxRGB!(240, 128, 128); + enum lightcyan = gxRGB!(224, 255, 255); + enum lightgoldenrodyellow = gxRGB!(250, 250, 210); + enum lightgray = gxRGB!(211, 211, 211); + enum lightgreen = gxRGB!(144, 238, 144); + enum lightgrey = gxRGB!(211, 211, 211); + enum lightpink = gxRGB!(255, 182, 193); + enum lightsalmon = gxRGB!(255, 160, 122); + enum lightseagreen = gxRGB!(32, 178, 170); + enum lightskyblue = gxRGB!(135, 206, 250); + enum lightslategray = gxRGB!(119, 136, 153); + enum lightslategrey = gxRGB!(119, 136, 153); + enum lightsteelblue = gxRGB!(176, 196, 222); + enum lightyellow = gxRGB!(255, 255, 224); + enum lime = gxRGB!(0, 255, 0); + enum limegreen = gxRGB!(50, 205, 50); + enum linen = gxRGB!(250, 240, 230); + enum magenta = gxRGB!(255, 0, 255); // basic color + enum maroon = gxRGB!(128, 0, 0); + enum mediumaquamarine = gxRGB!(102, 205, 170); + enum mediumblue = gxRGB!(0, 0, 205); + enum mediumorchid = gxRGB!(186, 85, 211); + enum mediumpurple = gxRGB!(147, 112, 219); + enum mediumseagreen = gxRGB!(60, 179, 113); + enum mediumslateblue = gxRGB!(123, 104, 238); + enum mediumspringgreen = gxRGB!(0, 250, 154); + enum mediumturquoise = gxRGB!(72, 209, 204); + enum mediumvioletred = gxRGB!(199, 21, 133); + enum midnightblue = gxRGB!(25, 25, 112); + enum mintcream = gxRGB!(245, 255, 250); + enum mistyrose = gxRGB!(255, 228, 225); + enum moccasin = gxRGB!(255, 228, 181); + enum navajowhite = gxRGB!(255, 222, 173); + enum navy = gxRGB!(0, 0, 128); + enum oldlace = gxRGB!(253, 245, 230); + enum olive = gxRGB!(128, 128, 0); + enum olivedrab = gxRGB!(107, 142, 35); + enum orange = gxRGB!(255, 165, 0); + enum orangered = gxRGB!(255, 69, 0); + enum orchid = gxRGB!(218, 112, 214); + enum palegoldenrod = gxRGB!(238, 232, 170); + enum palegreen = gxRGB!(152, 251, 152); + enum paleturquoise = gxRGB!(175, 238, 238); + enum palevioletred = gxRGB!(219, 112, 147); + enum papayawhip = gxRGB!(255, 239, 213); + enum peachpuff = gxRGB!(255, 218, 185); + enum peru = gxRGB!(205, 133, 63); + enum pink = gxRGB!(255, 192, 203); + enum plum = gxRGB!(221, 160, 221); + enum powderblue = gxRGB!(176, 224, 230); + enum purple = gxRGB!(128, 0, 128); + enum red = gxRGB!(255, 0, 0); // basic color + enum rosybrown = gxRGB!(188, 143, 143); + enum royalblue = gxRGB!(65, 105, 225); + enum saddlebrown = gxRGB!(139, 69, 19); + enum salmon = gxRGB!(250, 128, 114); + enum sandybrown = gxRGB!(244, 164, 96); + enum seagreen = gxRGB!(46, 139, 87); + enum seashell = gxRGB!(255, 245, 238); + enum sienna = gxRGB!(160, 82, 45); + enum silver = gxRGB!(192, 192, 192); + enum skyblue = gxRGB!(135, 206, 235); + enum slateblue = gxRGB!(106, 90, 205); + enum slategray = gxRGB!(112, 128, 144); + enum slategrey = gxRGB!(112, 128, 144); + enum snow = gxRGB!(255, 250, 250); + enum springgreen = gxRGB!(0, 255, 127); + enum steelblue = gxRGB!(70, 130, 180); + enum tan = gxRGB!(210, 180, 140); + enum teal = gxRGB!(0, 128, 128); + enum thistle = gxRGB!(216, 191, 216); + enum tomato = gxRGB!(255, 99, 71); + enum turquoise = gxRGB!(64, 224, 208); + enum violet = gxRGB!(238, 130, 238); + enum wheat = gxRGB!(245, 222, 179); + enum white = gxRGB!(255, 255, 255); // basic color + enum whitesmoke = gxRGB!(245, 245, 245); + enum yellow = gxRGB!(255, 255, 0); // basic color + enum yellowgreen = gxRGB!(154, 205, 50); +} + + +// A-HSL color +public align(1) struct GxColorHSL { +align(1): + float h=0.0f, s=0.0f, l=1.0f, a=1.0f; + + string toString () const nothrow { + import core.stdc.stdio : snprintf; + char[64] buf = void; + if (a == 1.0f) { + immutable len = snprintf(buf.ptr, buf.length, "HSL(%g,%g,%g)", h, s, l); + return buf[0..len].idup; + } else { + immutable len = snprintf(buf.ptr, buf.length, "HSL(%g,%g,%g,%g)", h, s, l, a); + return buf[0..len].idup; + } + } + + public static T clampval(T) (in T a, in T mn, in T mx) pure nothrow @trusted @nogc { pragma(inline, true); return (a < mn ? mn : a > mx ? mx : a); } + +nothrow @safe @nogc: +private: + static float calchue (float h, in float m1, in float m2) pure nothrow @safe @nogc { + if (h < 0) h += 1; + if (h > 1) h -= 1; + if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; + if (h < 3.0f/6.0f) return m2; + if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; + return m1; + } + +public: + this (in float ah, in float as, in float al, in float aa=1.0f) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } + + this (in uint clr) pure { pragma(inline, true); fromColor(clr); } + + uint asColor () const { + import core.stdc.math : fmodf; + //static if (__VERSION__ >= 2072) pragma(inline, true); + float xh = fmodf(h, 1.0f); + if (xh < 0.0f) xh += 1.0f; + immutable float xs = clampval(s, 0.0f, 1.0f); + immutable float xl = clampval(l, 0.0f, 1.0f); + immutable m2 = (xl <= 0.5f ? xl*(1+xs) : xl+xs-xl*xs); + immutable m1 = 2*xl-m2; + return + (clampToByte(cast(int)(clampval(calchue(xh+1.0f/3.0f, m1, m2), 0.0f, 1.0f)*255.0f))<<16)| + (clampToByte(cast(int)(clampval(calchue(xh, m1, m2), 0.0f, 1.0f)*255.0f))<<8)| + clampToByte(cast(int)(clampval(calchue(xh-1.0f/3.0f, m1, m2), 0.0f, 1.0f)*255.0f))| + (clampToByte(cast(int)(clampval(a, 0.0, 1.0f)*255.0f))<<24); + } + + // taken from Adam's arsd.color + /* Converts an RGB color into an HSL triplet. + * [useWeightedLightness] will try to get a better value for luminosity for the human eye, + * which is more sensitive to green than red and more to red than blue. + * If it is false, it just does average of the rgb. */ + void fromColor (in uint c, bool useWeightedLightness=false) pure @trusted { + this.a = gxGetAlpha(c)/255.0f; + immutable float r1 = gxGetRed(c)/255.0f; + immutable float g1 = gxGetGreen(c)/255.0f; + immutable float b1 = gxGetBlue(c)/255.0f; + + float maxColor = r1; + if (g1 > maxColor) maxColor = g1; + if (b1 > maxColor) maxColor = b1; + float minColor = r1; + if (g1 < minColor) minColor = g1; + if (b1 < minColor) minColor = b1; + + this.l = (maxColor+minColor)*0.5f; + if (useWeightedLightness) { + // the colors don't affect the eye equally + // this is a little more accurate than plain HSL numbers + this.l = 0.2126*r1+0.7152*g1+0.0722*b1; + } + if (maxColor != minColor) { + if (this.l < 0.5f) { + this.s = (maxColor-minColor)/(maxColor+minColor); + } else { + this.s = (maxColor-minColor)/(2.0f-maxColor-minColor); + } + if (r1 == maxColor) { + this.h = (g1-b1)/(maxColor-minColor); + } else if (g1 == maxColor) { + this.h = 2.0f+(b1-r1)/(maxColor-minColor); + } else { + this.h = 4.0f+(r1-g1)/(maxColor-minColor); + } + } + + this.h = this.h*60.0f; + if (this.h < 0.0f) this.h += 360.0f; + this.h /= 360.0f; + } +} + + +// ////////////////////////////////////////////////////////////////////////// // // current clip rect public __gshared GxRect gxClipRect = GxRect(65535, 65535); diff --git a/egra/test.d b/egra/test.d index e8dcf1a..e947958 100644 --- a/egra/test.d +++ b/egra/test.d @@ -336,19 +336,24 @@ final class MainPaneWindow : SubWindow { float baphy = (screenHeight-gxagg.BaphometDims)*0.5f; gxagg.renderBaphomet(baphx+ofs, baphy+ofs); version(none) { - gxagg.moveTo(baphx-1+ofs, baphy-1+ofs); - gxagg.lineTo(baphx+gxagg.BaphometDims+ofs, baphy-1+ofs); - gxagg.lineTo(baphx+gxagg.BaphometDims+ofs, baphy+gxagg.BaphometDims+ofs); - gxagg.lineTo(baphx-1+ofs, baphy+gxagg.BaphometDims+ofs); - gxagg.lineTo(baphx-1+ofs, baphy-1+ofs); + gxagg + .moveTo(baphx-1+ofs, baphy-1+ofs) + .lineTo(baphx+gxagg.BaphometDims+ofs, baphy-1+ofs) + .lineTo(baphx+gxagg.BaphometDims+ofs, baphy+gxagg.BaphometDims+ofs) + .lineTo(baphx-1+ofs, baphy+gxagg.BaphometDims+ofs) + .lineTo(baphx-1+ofs, baphy-1+ofs); + } + version(all) { + gxagg.roundedRect(baphx-1+ofs, baphy-1+ofs, gxagg.BaphometDims+1, gxagg.BaphometDims+1, 6.0f); } gxagg.stroke(); version(none) { - gxagg.beginPath(); - gxagg.moveTo(10, 10); - gxagg.lineTo(30, 20); - gxagg.width = 1.0f; + gxagg + .beginPath(); + .moveTo(10, 10); + .lineTo(30, 20); + .width = 1.0f; gxagg.contour(); } gxagg.endFrame(gxRGBA!(255, 0, 0, 42)); -- 2.11.4.GIT