From 6920cd287cd778faf8c9f49a0a2d7e8c334fe4a9 Mon Sep 17 00:00:00 2001 From: ketmar Date: Wed, 1 Dec 2021 05:48:15 +0000 Subject: [PATCH] egra: better, faster, and more flexible vertical gradients in agg mini FossilOrigin-Name: c7a22c0af63c899f8ce215e4ed06582cb43a825e14da3eadfc3514310ae64f19 --- egra/gfx/aggmini/core.d | 32 +++++++++++------ egra/gfx/aggmini/enums.d | 90 ++++++++++++++++++++++++++++++++++++++++++++++ egra/gfx/aggmini/render.d | 49 ++++++------------------- egra/gfx/base.d | 17 --------- egra/gfx/lowlevel.d | 68 ++++++++++++++++++++++++++++++++++- egra/gui/widgets/buttons.d | 6 ++-- egra/test.d | 16 +++++++-- 7 files changed, 207 insertions(+), 71 deletions(-) diff --git a/egra/gfx/aggmini/core.d b/egra/gfx/aggmini/core.d index dae24ad..3b0fe8a 100644 --- a/egra/gfx/aggmini/core.d +++ b/egra/gfx/aggmini/core.d @@ -294,6 +294,15 @@ public: @property float realCurrX () const pure { pragma(inline, true); return vtxState.lastX; } @property float realCurrY () const pure { pragma(inline, true); return vtxState.lastY; } + // returns current rasterized bounds + // rasterization is done by `fill()`, `stroke()`, and so on + @property GxRect rasterBounds () const pure { pragma(inline, true); return GxRect(rast.minX, rast.minY, rast.maxX-rast.minX+1, rast.maxY-rast.minY+1); } + + @property int rasterMinX () const pure { pragma(inline, true); return rast.minX; } + @property int rasterMinY () const pure { pragma(inline, true); return rast.minY; } + @property int rasterMaxX () const pure { pragma(inline, true); return rast.maxX; } + @property int rasterMaxY () const pure { pragma(inline, true); return rast.maxY; } + void resetTransform () { pragma(inline, true); transform.identity(); } @@ -454,26 +463,29 @@ public: ref AGGDrawer render (in GxColor c) { pragma(inline, true); return renderWith(params.fillRule, c); } - ref AGGDrawer renderVGradientWith (in FillRule aFillRule, in GxColor c0, in GxColor c1, in float midp=-1.0f, in GxColor midc=gxUnknown) { + struct VGradient { + int[4] yp; + GxColor[4] clr; + } + + ref AGGDrawer renderVGradientWith(VG) (in FillRule aFillRule, in GxColor baseclr, in auto ref VG grad) + if (IsGoodVerticalGradient!VG) + { GxRect clipRect = gxClipRect; if (clipRect.intersect(0, 0, VBufWidth, VBufHeight)) { // yeah, fill rule matters only at this stage rast.fillRule = aFillRule; - rast.useGradient = true; - rast.gradC0 = c0; - rast.gradC1 = c1; - rast.gradMidP = midp; - rast.gradMidC = midc; - scope(exit) rast.useGradient = false; - rast.render(clipRect, gxTransparent); + rast.render(clipRect, baseclr, grad); } rast.reset(); return this; } - ref AGGDrawer renderVGradient (in GxColor c0, in GxColor c1, in float midp=-1.0f, in GxColor midc=gxUnknown) { + ref AGGDrawer renderVGradient(VG) (in GxColor baseclr, in auto ref VG grad) + if (IsGoodVerticalGradient!VG) + { pragma(inline, true); - return renderVGradientWith(params.fillRule, c0, c1, midp, midc); + return renderVGradientWith(params.fillRule, baseclr, grad); } diff --git a/egra/gfx/aggmini/enums.d b/egra/gfx/aggmini/enums.d index ae3a864..3b8c091 100644 --- a/egra/gfx/aggmini/enums.d +++ b/egra/gfx/aggmini/enums.d @@ -20,6 +20,9 @@ module iv.egra.gfx.aggmini.enums; private: nothrow @safe @nogc: +private import iv.egra.gfx.lowlevel : gxInterpolateColorI; +private import iv.egra.gfx.base : GxColor, gxTransparent; + /* Bezier curve rasterizer. * @@ -129,3 +132,90 @@ public: mMiterLimit = v; } } + + +// ////////////////////////////////////////////////////////////////////////// // +/* + vertical gradient interface: + bool isValid (); + GxColor colorAtY (int y, in GxColor cbase); +*/ + +public template IsGoodVerticalGradient(VG) { + enum IsGoodVerticalGradient = + is(typeof((inout int=0) { + VG vg = void; + bool v = vg.isValid; + int y; + const GxColor cbase; + GxColor c = vg.colorAtY(y, cbase); + })); +} + + +public struct AGGInvalidGradient { + enum isValid = false; + GxColor colorAtY (int y, in GxColor cbase) const pure nothrow @safe @nogc { return cbase; } +} + + +// 2 or 3 colors +// 2 if clr[2] is 0 +public struct AGGVGradient3 { +private: + int[3] yp; + GxColor[3] clr; // 0 is transparent color, what a coincidence! + +public: + bool isValid; + +nothrow @trusted @nogc: +public: + this (in int y0, in GxColor c0, in int y1, in GxColor c1, in int midpercent=-1, in GxColor midc=gxTransparent) { + pragma(inline, true); + set(y0, c0, y1, c1, midpercent, midc); + } + + void set (in int y0, in GxColor c0, in int y1, in GxColor c1, in int midpercent=-1, in GxColor midc=gxTransparent) { + isValid = true; + if (y0 < y1) { + yp.ptr[0] = y0; + clr.ptr[0] = c0; + yp.ptr[1] = y1; + clr.ptr[1] = c1; + } else { + yp.ptr[0] = y1; + clr.ptr[0] = c1; + yp.ptr[1] = y0; + clr.ptr[1] = c0; + } + // no third point yet (i.e. max) + yp.ptr[2] = yp.ptr[1]; + clr.ptr[2] = clr.ptr[1]; + // check for valid midpoint + if (midpercent < 0 || midpercent > 100) return; + // check for min or max midpoint + if (midpercent == 0) { clr.ptr[0] = midc; return; } + if (midpercent == 100) { clr.ptr[1] = clr.ptr[2] = midc; return; } + // add midpoint + yp.ptr[2] = yp.ptr[1]; + clr.ptr[2] = clr.ptr[1]; + yp.ptr[1] = yp.ptr[0]+(yp.ptr[1]-yp.ptr[0])*midpercent/100; + clr.ptr[1] = midc; + } + + GxColor colorAtY (int y, in GxColor cbase) const pure { + if (y <= yp.ptr[0]) return clr.ptr[0]; + if (y < yp.ptr[1] && yp.ptr[0] != yp.ptr[1]) { + // scale to [0..65535] + immutable int t = ((y-yp.ptr[0])<<16)/(yp.ptr[1]-yp.ptr[0]); + return gxInterpolateColorI(clr.ptr[0], clr.ptr[1], t); + } + if (clr.ptr[1] != clr.ptr[2] && y < yp.ptr[2] && yp.ptr[1] != yp.ptr[2]) { + // scale to [0..65535] + immutable int t = ((y-yp.ptr[1])<<16)/(yp.ptr[2]-yp.ptr[1]); + return gxInterpolateColorI(clr.ptr[1], clr.ptr[2], t); + } + return clr.ptr[2]; + } +} diff --git a/egra/gfx/aggmini/render.d b/egra/gfx/aggmini/render.d index e59cd89..fc06458 100644 --- a/egra/gfx/aggmini/render.d +++ b/egra/gfx/aggmini/render.d @@ -46,7 +46,7 @@ private: nothrow @safe @nogc: private import iv.egra.gfx.aggmini : DisableCopyingMixin; -private import iv.egra.gfx.aggmini.enums : FillRule; +private import iv.egra.gfx.aggmini.enums; import iv.bclamp; import iv.egra.gfx.base; @@ -875,13 +875,6 @@ private: ubyte[256] mGamma = DefaultGamma[]; public: - bool useGradient; - uint gradC0; - uint gradC1; - float gradMidP = -1.0f; - uint gradMidC; - -public: mixin(DisableCopyingMixin); void reset () { mOutline.reset(); } @@ -937,47 +930,25 @@ public: } void render (in ref GxRect clipRect, in GxColor c, int dx=0, int dy=0) { + render(clipRect, c, AGGInvalidGradient(), dx, dy); + } + + void render(VG) (in ref GxRect clipRect, in GxColor c, in auto ref VG grad, int dx=0, int dy=0) + if (IsGoodVerticalGradient!VG) + { const(Cell)** cells = mOutline.cells(); if (mOutline.numCells() == 0) return; - immutable bool grad = useGradient; - float gmp = gradMidP; - bool useMidP = false; - float gradY0, gradY1, gradTotal; - uint gC0, gC1, gCM; - if (grad) { - gC0 = gradC0; - gC1 = gradC1; - gCM = gradMidC; - gradY0 = minY; - gradY1 = maxY; - if (gmp == 0.0f) { gC1 = gCM; gmp = -1.0f; } - useMidP = (gmp > 0.0f && gmp < 1.0f && minY < maxY); - gradTotal = 1.0f/(gradY1-gradY0+1.0f); - } else { - if (gxIsTransparent(c)) return; - } + immutable bool useGrad = grad.isValid; void flushScanline () { if (mScanline.y < clipRect.pos.y || mScanline.y > clipRect.y1) return; - if (!grad) { + if (!useGrad) { // no gradient Renderer.render(mScanline, c, clipRect); - return; - } - // vertical gradient - uint clr = void; - immutable float gt = (cast(float)mScanline.y-gradY0)*gradTotal; - if (useMidP) { - if (gt < gmp) { - clr = gxInterpolateColor(gC0, gCM, gt/gmp); - } else { - clr = gxInterpolateColor(gCM, gC1, (gt-gmp)/(1.0f-gmp)); - } } else { - clr = gxInterpolateColor(gC0, gC1, gt); + Renderer.render(mScanline, grad.colorAtY(mScanline.y, c), clipRect); } - Renderer.render(mScanline, clr, clipRect); } mScanline.reset(mOutline.minX, mOutline.maxX, dx, dy); diff --git a/egra/gfx/base.d b/egra/gfx/base.d index 910bfc1..f1e2415 100644 --- a/egra/gfx/base.d +++ b/egra/gfx/base.d @@ -635,23 +635,6 @@ public uint gxColMix (in uint dc, in uint clr) pure nothrow @trusted @nogc { } -public int gxInterpolateColor (in uint c0, in uint c1, in float t) pure nothrow @safe @nogc { - if (t <= 0.0f) return c0; - if (t >= 1.0f) return c1; - - static ubyte interpByte (in ubyte b0, in ubyte b1, in float t) pure nothrow @safe @nogc { - pragma(inline, true); - return (b0 == b1 ? b0 : clampToByte(b0+cast(int)((cast(int)b1-cast(int)b0)*t))); - } - - immutable ubyte r = interpByte(gxGetRed(c0), gxGetRed(c1), t); - immutable ubyte g = interpByte(gxGetGreen(c0), gxGetGreen(c1), t); - immutable ubyte b = interpByte(gxGetBlue(c0), gxGetBlue(c1), t); - immutable ubyte a = interpByte(gxGetAlpha(c0), gxGetAlpha(c1), t); - return (a<<24)|(r<<16)|(g<<8)|b; -} - - // ////////////////////////////////////////////////////////////////////////// // private template isGoodRGBInt(T) { import std.traits : Unqual; diff --git a/egra/gfx/lowlevel.d b/egra/gfx/lowlevel.d index f08cddf..9f808bb 100644 --- a/egra/gfx/lowlevel.d +++ b/egra/gfx/lowlevel.d @@ -28,7 +28,7 @@ version(egfx_disable_sse41) { static assert(false, "EGRA: SSE4.1 is both forced and disabled. wtf?!"); } } else { - version(DigitalMars) { + version(D_InlineAsm_X86) { version(X86) { version = egfx_use_sse41; } else { @@ -98,6 +98,72 @@ public enum GxColMixMixin(string destvar, string dcvar, string colvar) = `{ } +// t is [0..1] +public int gxInterpolateColorF (in uint c0, in uint c1, in float t) pure nothrow @safe @nogc { + import iv.bclamp; + import iv.egra.gfx.base; + + if (t <= 0.0f) return c0; + if (t >= 1.0f) return c1; + + static ubyte interpByte (in ubyte b0, in ubyte b1, in float t) pure nothrow @safe @nogc { + pragma(inline, true); + return (b0 == b1 ? b0 : clampToByte(b0+cast(int)((cast(int)b1-cast(int)b0)*t))); + } + + immutable ubyte r = interpByte(gxGetRed(c0), gxGetRed(c1), t); + immutable ubyte g = interpByte(gxGetGreen(c0), gxGetGreen(c1), t); + immutable ubyte b = interpByte(gxGetBlue(c0), gxGetBlue(c1), t); + immutable ubyte a = interpByte(gxGetAlpha(c0), gxGetAlpha(c1), t); + return (a<<24)|(r<<16)|(g<<8)|b; +} + + +// t is [0..65535] +public int gxInterpolateColorI (in uint c0, in uint c1, in int t) pure nothrow @safe @nogc { + if (t <= 0) return c0; + if (t >= 65535) return c1; + if (c0 == c1) return c0; + + version(all) { + immutable uint a_ = cast(uint)(t>>8)+1; // to not loose bits + uint rb_ = c0&0xff00ffu; + uint g_ = c0&0x00ff00u; + rb_ += (((c1&0xff00ffu)-rb_)*a_)>>8; + g_ += (((c1&0x00ff00u)-g_)*a_)>>8; + /* g is mixed with solid alpha; replace "0xff_" with other alpha if you want to */ + immutable uint res = (rb_&0xff00ffu)|(g_&0x00ff00u); + // now mix alpha + immutable int a0 = (c0>>24); + immutable int a1 = (c1>>24); + // same alpha? + if (a0 == a1) return res|(c0&0xff000000u); + // mix both alphas + return res|(((((a1-a0)*(t+1))>>16)+a0)<<24); + } else { + //return gxInterpolateColorF(c0, c1, cast(float)t/65535.0); + + int b0 = cast(int)cast(ubyte)c0; + int b1 = cast(int)cast(ubyte)c1; + if (b0 != b1) b0 = cast(ubyte)((((b1-b0)*(t+1))>>16)+b0); + + int g0 = cast(int)cast(ubyte)(c0>>8); + int g1 = cast(int)cast(ubyte)(c1>>8); + if (g0 != g1) g0 = cast(ubyte)((((g1-g0)*(t+1))>>16)+g0); + + int r0 = cast(int)cast(ubyte)(c0>>16); + int r1 = cast(int)cast(ubyte)(c1>>16); + if (r0 != r1) r0 = cast(ubyte)((((r1-r0)*(t+1))>>16)+r0); + + int a0 = cast(int)cast(ubyte)(c0>>24); + int a1 = cast(int)cast(ubyte)(c1>>24); + if (a0 != a1) a0 = cast(ubyte)((((a1-a0)*(t+1))>>16)+a0); + + return cast(uint)((a0<<24)|(r0<<16)|(g0<<8)|b0); + } +} + + // ////////////////////////////////////////////////////////////////////////// // // size is in dwords version(egfx_use_sse41) { diff --git a/egra/gui/widgets/buttons.d b/egra/gui/widgets/buttons.d index 5cbf28d..d8dec82 100644 --- a/egra/gui/widgets/buttons.d +++ b/egra/gui/widgets/buttons.d @@ -110,12 +110,14 @@ public: gxVLine(grect.x1+0, grect.y0+1, grect.height-2, bclr); } else { immutable float rad = (height < 16 ? height*0.5f : 6); + auto grad = AGGVGradient3(grect.y0, getColor("grad0-back"), + grect.y1, getColor("grad2-back"), + getInt("grad-midp"), getColor("grad1-back")); gxagg .beginPath() .roundedRect(grect.x0+0.5f, grect.y0+0.5f, width, height, rad) .fill() - .renderVGradientWith(AGGParams.init.fillRule, getColor("grad0-back"), getColor("grad2-back"), - getInt("grad-midp")/100.0f, getColor("grad1-back")); + .renderVGradientWith(AGGParams.init.fillRule, bclr, grad); ty += 1; } gxClipRect.shrinkBy(1, 1); diff --git a/egra/test.d b/egra/test.d index dcdfa46..a57c438 100644 --- a/egra/test.d +++ b/egra/test.d @@ -504,12 +504,22 @@ final class MainPaneWindow : SubWindow { //gxagg.render(gxrgba(0, 127, 0, 127)); version(all) { + AGGVGradient3 grad; gxagg .beginPath() .roundedRect(400.5f, 600.5f, 96, 28, 8) .fill() //.render(gxRGB!(192, 192, 192)); - .renderVGradient(gxRGB!(127, 127, 127), gxRGB!(142, 142, 142), 0.2f, gxRGB!(196, 196, 196)); + /* + .renderVGradient(gxTransparent, + AGGVGradient3(gxagg.rasterMinY, gxRGB!(127, 127, 127), + gxagg.rasterMaxY, gxRGB!(142, 142, 142), + 20, gxRGB!(196, 196, 196))); + */ + .renderVGradient(gxTransparent, + AGGVGradient3(gxagg.rasterMinY, gxRGB!(255, 0, 0), + gxagg.rasterMaxY, gxRGB!(0, 255, 0), + 20, gxRGB!(0, 0, 255))); } version(all) { @@ -534,7 +544,9 @@ final class MainPaneWindow : SubWindow { //.stroke() .fill() //.render(GxColors.k8orange); - .renderVGradient(gxRGB!(200, 73, 0), gxRGB!(255, 128, 0)); + .renderVGradient(gxTransparent, + AGGVGradient3(gxagg.rasterMinY, gxRGB!(200, 73, 0), + gxagg.rasterMaxY, gxRGB!(255, 128, 0))); } int emb = cast(int)(4/scale); float[4] ebounds; -- 2.11.4.GIT