GDI+ line styles (dashed line) not working

Version: Current retail

Frequency: Consistently

Severity: Low/High/Blocker

Context: GDI+ WASM gauge development

Bug description: GDI+ does not render a dashed line style

Repro steps: Create a gauge that uses dashed line style. The line will be rendered solid

Attachments:

Private attachments: Send a PM to @PrivateContent with the link to this topic and the link to download your content

I am developing instrumentation that uses dashe line style, and after repeated attempts, the dashed line style refuses to render as advertised - it renders as solid.

Here is my condensed code:

Pen penPurpleDashThin; // declaration
penPurpleDashThin(Color(255, 0, 204)), // implicit init
penPurpleDashThin.SetDashStyle(DashStyle::DashStyleDash); // set style
gfx->DrawLine(&penPurpleDashThin, 0, 0, 0, 518); // usage => Renders as a solid line.

I also use hatched brushes which are similarly defined, and they work fine.

Thanks,
Mitch
TerraBuilder Team

Hi. I’ve added a few features to gdiplus for my own use. For now, this is enough for my current tasks. Maybe it’ll help someone else.

Add to GdiPlusGraphics.h

Status DrawDashLine(const Pen* pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL dashLen, REAL gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashLine(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x1, y1, x2, y2, dashLen, gapLen));
}
Status DrawDashLine(const Pen* pen, INT x1, INT y1, INT x2, INT y2, INT dashLen, INT gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashLineI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x1, y1, x2, y2, dashLen, gapLen));
}
Status DrawDashLine(const Pen* pen, const PointF& pt1, const PointF& pt2, REAL dashLen, REAL gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashLine(
                nativeGraphics, pen ? pen->nativePen : NULL,
                pt1.X, pt1.Y, pt2.X, pt2.Y, dashLen, gapLen));
}
Status DrawDashLine(const Pen* pen, const Point& pt1, const Point& pt2, INT dashLen, INT gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashLineI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                pt1.X, pt1.Y, pt2.X, pt2.Y, dashLen, gapLen));
}
Status DrawDashArc(const Pen* pen, REAL x, REAL y, REAL width, REAL height,
                       REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
            return updateStatus(DLLExports::GdipDrawDashArc(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x, y, width, height, startAngle, sweepAngle, dashAngle, gapAngle));
}
Status DrawDashArc(const Pen* pen, INT x, INT y, INT width, INT height,
                       REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
            return updateStatus(DLLExports::GdipDrawDashArcI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x, y, width, height, startAngle, sweepAngle, dashAngle, gapAngle));
}
Status DrawDashArc(const Pen* pen, const RectF& rect,
                       REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
            return updateStatus(DLLExports::GdipDrawDashArc(
                nativeGraphics, pen ? pen->nativePen : NULL,
                rect.X, rect.Y, rect.Width, rect.Height,
                startAngle, sweepAngle, dashAngle, gapAngle));
}
Status DrawDashArc(const Pen* pen, const Rect& rect,
                       REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
            return updateStatus(DLLExports::GdipDrawDashArcI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                rect.X, rect.Y, rect.Width, rect.Height,
                startAngle, sweepAngle, dashAngle, gapAngle));
}
Status DrawDashBezier(const Pen* pen,
                          REAL x1, REAL y1, REAL x2, REAL y2,
                          REAL x3, REAL y3, REAL x4, REAL y4, REAL dashLen, REAL gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashBezier(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x1, y1, x2, y2, x3, y3, x4, y4, dashLen, gapLen));
}
Status DrawDashBezier(const Pen* pen,
                          INT x1, INT y1, INT x2, INT y2,
                          INT x3, INT y3, INT x4, INT y4, INT dashLen, INT gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashBezierI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                x1, y1, x2, y2, x3, y3, x4, y4, dashLen, gapLen));
}
Status DrawDashBezier(const Pen* pen,
                          const PointF& pt1, const PointF& pt2,
                          const PointF& pt3, const PointF& pt4, REAL dashLen, REAL gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashBezier(
                nativeGraphics, pen ? pen->nativePen : NULL,
                pt1.X, pt1.Y, pt2.X, pt2.Y,
                pt3.X, pt3.Y, pt4.X, pt4.Y, dashLen, gapLen));
}
Status DrawDashBezier(const Pen* pen, const Point& pt1, const Point& pt2, const Point& pt3, const Point& pt4, INT dashLen, INT gapLen)
{
            return updateStatus(DLLExports::GdipDrawDashBezierI(
                nativeGraphics, pen ? pen->nativePen : NULL,
                pt1.X, pt1.Y, pt2.X, pt2.Y,
                pt3.X, pt3.Y, pt4.X, pt4.Y, dashLen, gapLen));
}


To graphics.cpp

GpStatus WINGDIPAPI GdipDrawDashLine(GpGraphics* graphics, GpPen* pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL dashLen, REAL gapLen)
{
        convertPen(graphics, pen);

        float dx = x2 - x1;
        float dy = y2 - y1;
        float len =  sqrtf(dx * dx + dy * dy);
        dx /= len;
        dy /= len;

        for(float dist = 0; dist < len; dist += (dashLen + gapLen)) {
            float d = (dist + dashLen > len) ? (len - dist) : dashLen;
            nvgBeginPath(graphics->ctx);
            nvgMoveTo(graphics->ctx, x1 + dx * dist, y1 + dy * dist);
            nvgLineTo(graphics->ctx, x1 + dx * (dist + d), y1 + dy * (dist + d));
            nvgStroke(graphics->ctx);
        }

		return GpStatus::Ok;
}

GpStatus WINGDIPAPI GdipDrawDashLineI(GpGraphics* graphics, GpPen* pen, INT x1, INT y1, INT x2, INT y2, INT dashLen, INT gapLen)
{
        return GdipDrawDashLine(graphics, pen, (REAL)x1, (REAL)y1, (REAL)x2, (REAL)y2, (REAL)dashLen, (REAL)gapLen);
}

GpStatus WINGDIPAPI GdipDrawDashArc(GpGraphics* graphics, GpPen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
    #ifdef GDIFLAT_SAFE
        if(!graphics || !graphics->ctx)
            return GpStatus::InvalidParameter;
    #endif
        convertPen(graphics, pen);
        for(float a = nvgDegToRad(startAngle); a < nvgDegToRad(sweepAngle); a += (dashAngle + gapAngle)) {
            float segmentEnd = a + dashAngle;
            if(segmentEnd > sweepAngle) {
                segmentEnd = sweepAngle;
            }

            nvgBeginPath(graphics->ctx);
			nvgEllipticalArc(graphics->ctx, x + width * 0.5f, y + height * 0.5f, width * 0.5f, height * 0.5f, a, segmentEnd, (sweepAngle < 0 ? NVG_CCW : NVG_CW));
            nvgStroke(graphics->ctx);
        }
		return GpStatus::Ok;
}

GpStatus WINGDIPAPI GdipDrawDashArcI(GpGraphics* graphics, GpPen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle)
{
        return GdipDrawDashArc(graphics, pen, (REAL)x, (REAL)y, (REAL)width, (REAL)height, startAngle, sweepAngle, dashAngle, gapAngle);
}

GpStatus WINGDIPAPI GdipDrawDashBezier(GpGraphics* graphics, GpPen* pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL x3, REAL y3,
                       REAL x4, REAL y4, REAL dashLen, REAL gapLen)
    {
    #ifdef GDIFLAT_SAFE
        if(!graphics || !graphics->ctx)
            return GpStatus::InvalidParameter;
    #endif
        convertPen(graphics, pen);

		float lastX = x1, lastY = y1;
        float currentDist = 0;
        bool drawing = true;

        auto getBezierPt = [](float t,
                              float x1,
                              float y1,
                              float x2,
                              float y2,
                              float x3,
                              float y3,
                              float x4,
                              float y4) -> std::tuple<float, float> {
                                  float c = 1.0f - t;
                                  float c2 = c * c;
                                  float c3 = c2 * c;
                                  float t2 = t * t;
                                  float t3 = t2 * t;
                                  float outX = c3 * x1 + 3 * c2 * t * x2 + 3 * c * t2 * x3 + t3 * x4;
                                  float outY = c3 * y1 + 3 * c2 * t * y2 + 3 * c * t2 * y3 + t3 * y4;
                                  return {outX, outY};
            };

        nvgBeginPath(graphics->ctx);
        nvgMoveTo(graphics->ctx, x1, y1);

        for(float t = 0.01f; t <= 1.01f; t += 0.01f) {
            auto [px, py] = getBezierPt(fminf(t, 1.0f), x1, y1, x2, y2, x3, y3, x4, y4);

            float dist = sqrtf((px - lastX) * (px - lastX) + (py - lastY) * (py - lastY));
            currentDist += dist;

            if(drawing && currentDist >= dashLen) {
                nvgLineTo(graphics->ctx, px, py);
                drawing = false;
                currentDist = 0;
            } else if(!drawing && currentDist >= gapLen) {
                nvgMoveTo(graphics->ctx, px, py);
                drawing = true;
                currentDist = 0;
            } else if(drawing) {
                nvgLineTo(graphics->ctx, px, py);
            }
            lastX = px;
            lastY = py;
        }
        nvgStroke(graphics->ctx);
        return GpStatus::Ok;
}

GpStatus WINGDIPAPI GdipDrawDashBezierI(GpGraphics* graphics, GpPen* pen, INT x1, INT y1, INT x2, INT y2, INT x3, INT y3, INT x4, INT y4, INT dashLen, INT gapLen)
{
        return GdipDrawDashBezier(graphics, pen, (REAL)x1, (REAL)y1, (REAL)x2, (REAL)y2, (REAL)x3, (REAL)y3, (REAL)x4, (REAL)y4, (REAL)dashLen, (REAL)gapLen);
}

To graphics.h

GpStatus WINGDIPAPI GdipDrawDashLine(GpGraphics* graphics, GpPen* pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL dashLen, REAL gapLen);
GpStatus WINGDIPAPI GdipDrawDashLineI(GpGraphics* graphics, GpPen* pen, INT x1, INT y1, INT x2, INT y2, INT dashLen, INT gapLen);
GpStatus WINGDIPAPI GdipDrawDashArc(GpGraphics* graphics, GpPen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle);
GpStatus WINGDIPAPI GdipDrawDashArcI(GpGraphics* graphics, GpPen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle, REAL dashAngle, REAL gapAngle);
GpStatus WINGDIPAPI GdipDrawDashBezier(GpGraphics* graphics, GpPen* pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4, REAL dashLen, REAL gapLen);
GpStatus WINGDIPAPI GdipDrawDashBezierI(GpGraphics* graphics, GpPen* pen, INT x1, INT y1, INT x2, INT y2, INT x3, INT y3, INT x4, INT y4, INT dashLen, INT gapLen);
	

And samples

mGdi->Clear(Color::Black);
mGdi->DrawDashLine(&PenRed, 0, 0, 1000, 1000, 20, 10);
mGdi->DrawDashLine(&PenGreen, 500, 0, 500, 1000, 20, 20);
mGdi->DrawDashLine(&PenBlue, 1000, 0, 0, 1000, 30, 10);
mGdi->DrawDashLine(&PenWhite, 0, 500, 1000, 500, 10, 40);
mGdi->DrawDashArc(&PenPurple, 200, 200, 600, 600, 0, 180, 0.1, 0.1);
mGdi->DrawDashBezier(&PenYellow, 0, 800, 250, 200, 500, 800, 1000, 200, 20, 10);