diff --git a/gtkarea/ui.h b/gtkarea/ui.h index 77236099..8a77ae9c 100644 --- a/gtkarea/ui.h +++ b/gtkarea/ui.h @@ -56,7 +56,6 @@ enum uiDrawLineJoin { #define uiDrawDefaultMiterLimit 10.0 // TODOs -// - windows: SetPolyFillMode // - os x: FillPath/EOFillPath functions enum uiDrawFillMode { uiDrawFillModeWinding, diff --git a/winarea/draw.c b/winarea/draw.c index 956f0512..13e8376e 100644 --- a/winarea/draw.c +++ b/winarea/draw.c @@ -2,14 +2,7 @@ #include "area.h" struct uiDrawContext { - HDC mainDC; - - // when drawing in RGBA mode, we need to use this - HDC alphaDC; - HBITMAP alphaBitmap; - - // and this is the DC we actually draw to, either mainDC or alphaDC - HDC currentDC; + HDC dc; // source color BOOL useRGB; @@ -25,7 +18,7 @@ uiDrawContext *newContext(HDC dc) uiDrawContext *c; c = (uiDrawContext *) g_malloc0(sizeof (uiDrawContext)); - c->mainDC = dc; + c->dc = dc; return c; } @@ -36,31 +29,37 @@ void uiDrawBeginPathRGB(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b) c->r = r; c->g = g; c->b = b; - c->currentDC = c->mainDC; - if (BeginPath(c->currentDC) == 0) + if (BeginPath(c->dc) == 0) logLastError("error beginning new path in uiDrawBeginPathRGB()"); } void uiDrawBeginPathRGBA(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - // TODO + c->useRGB = TRUE; + c->useAlpha = TRUE; + c->r = r; + c->g = g; + c->b = b; + c->a = a; + if (BeginPath(c->dc) == 0) + logLastError("error beginning new path in uiDrawBeginPathRGB()"); } void uiDrawMoveTo(uiDrawContext *c, intmax_t x, intmax_t y) { - if (MoveToEx(c->currentDC, x, y, NULL) == 0) + if (MoveToEx(c->dc, x, y, NULL) == 0) logLastError("error moving to point in uiDrawMoveTo()"); } void uiDrawLineTo(uiDrawContext *c, intmax_t x, intmax_t y) { - if (LineTo(c->currentDC, x, y) == 0) + if (LineTo(c->dc, x, y) == 0) logLastError("error drawing line in uiDrawLineTo()"); } void uiDrawRectangle(uiDrawContext *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height) { - if (Rectangle(c->currentDC, x, y, x + width, y + height) == 0) + if (Rectangle(c->dc, x, y, x + width, y + height) == 0) logLastError("error drawing rectangle in uiDrawRectangle()"); } @@ -70,7 +69,7 @@ void uiDrawArc(uiDrawContext *c, intmax_t xCenter, intmax_t yCenter, intmax_t ra // TODO } // TODO convert radians to degrees - if (AngleArc(c->currentDC, + if (AngleArc(c->dc, xCenter, yCenter radius, startAngle, @@ -79,42 +78,266 @@ void uiDrawArc(uiDrawContext *c, intmax_t xCenter, intmax_t yCenter, intmax_t ra logLastError("error drawing arc in uiDrawArc()"); } +void uiDrawBezierTo(uiDrawContext *c, intmax_t c1x, intmax_t c1y, intmax_t c2x, intmax_t c2y, intmax_t endX, intmax_t endY) +{ + POINT points[3]; + + points[0].x = c1x; + points[0].y = c1y; + points[1].x = c2x; + points[2].y = c2y; + points[3].x = endX; + points[3].y = endY; + if (PolyBezierTo(c->dc, points, 3) == 0) + logLastError("error drawing bezier curve in uiDrawBezierTo()"); +} + void uiDrawCloseFigure(uiDrawContext *c) { if (CloseFigure(c->currentDC) == 0) logLastError("error closing figure in uiDrawCloseFigure()"); } -void uiDrawStroke(uiDrawContext *c, uiDrawStrokeParams *p) +static HPEN toPen(uiDrawContext *c, uiDrawStrokeParams *p) { - // TODO + DWORD style; + LOGBRUSH lb; + HPEN pen; + style = PS_GEOMETRIC; switch (p->Cap) { case uiDrawLineCapFlat: + style |= PS_ENDCAP_FLAT; break; case uiDrawLineCapRound: + style |= PS_ENDCAP_ROUND; break; case uiDrawLineCapSquare: + style |= PS_ENDCAP_SQUARE; break; } switch (p->Join) { case uiDrawLineJoinMiter: + style |= PS_JOIN_MITER; break; case uiDrawLineJoinRound: + style |= PS_JOIN_ROUND; break; case uiDrawLineJoinBevel: + style |= PS_JOIN_BEVEL; break; } + ZeroMemory(&lb, sizeof (LOGBRUSH)); + if (c->useRGB) { + lb.lbStyle = BS_SOLID; + // we don't need to worry about alpha premultiplication; see below + lb.lbColor = RGB(c->r, c->g, c->b); + } else { + // TODO + } + pen = ExtCreatePen(style, + p->Thickness, + &lb, + 0, + NULL); + if (pen == NULL) + logLastError("error creating pen in toPen()"); + return pen; +} + +// unfortunately alpha blending in GDI is limited to bitmap on bitmap +// to do alpha vector graphics, we have to fake it by drawing to an offscreen bitmap first +// also we can't just use regions, we have to copy the path... +// we don't need to worry about alpha premultiplication; see below +struct alpha { + HDC dc; + HBITMAP bitmap; + VOID *ppvBits; + HBITMAP prevbitmap; + RECT r; +}; + +static void startAlpha(uiDrawContext *c, struct alpha *a) +{ + int n; + POINT *points; + BYTE *ops; + HRGN region; + BITMAPINFO bi; + DWORD le; + + ZeroMemory(a, sizeof (struct alpha)); + + // first, get the path + // we need to do this first because PathToRegion() clears the path + SetLastError(0); + n = GetPath(c->dc, NULL, NULL, 0); + if (n == 0) { + le = GetLastError(); + SetLastError(le); // just in case + if (le != 0) + logLastError("error getting path point count in startAlpha()"); + } + // TODO switch to uiAlloc + points = (POINT *) malloc(n * sizeof (POINT)); + ops = (BYTE *) malloc(n * sizeof (BYTE)); + if (GetPath(c->dc, points, ops, n) != n) + logLastError("error getting path in startAlpha()"); + + // next we need to know the bounding box of the path so we can create the bitmap + region = PathToRegion(c->dc); + if (region == NULL) + logLastError("error getting path region in startAlpha()"); + if (GetRgnBox(region, &(a->r)) == 0) + logLastError("error getting region bounding box in startAlpha()"); + if (DeleteObject(region) == 0) + logLastError("error deleting path region in startAlpha()"); + + // now create a DC compatible with the original that we can copy the path to and AlphaBlend() from + a->dc = CreateCompatibleDC(c->dc); + if (a->dc == NULL) + logLastError("error creating compatible DC in startAlpha()"); + + // now create and select in a *device-independent* bitmap that will hold the data that we're going to alpha blend + ZeroMemory(&bmi, sizeof (BITMAPINFO)); + bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bi.bmiHeader.biWidth = a->r.right - a->r.left; + bi.bmiHeader.biHeight = -(a->r.bottom - a->r.top); // negative to draw top-down + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + a->bitmap = CreateDIBSection(c->dc, &bi, DIB_RGB_COLORS, + &(a->ppvBits), NULL, 0); + if (a->bitmap == NULL) + logLastError("error creating bitmap in startAlpha()"); + a->prevbitmap = SelectObject(a->dc, a->bitmap); + if (a->prevbitmap == NULL) + logLastError("error selecting bitmap into DC in startAlpha()"); + + // now we can finally copy the path like we were going to earlier + if (BeginPath(a->dc) == 0) + logLastError("error beginning path in startAlpha()"); + if (PolyDraw(a->dc, points, ops, n) != 0) + logLastError("error copying path in startAlpha()"); + if (EndPath(a->dc) == 0) + logLastError("error ending path in startAlpha()"); + free(points); + free(ops); +} + +static void finishAlpha(uiDrawContext *c, struct alpha *a) +{ + BLENDFUNCTION bf; + + bf.BlendOp = AC_SRC_OVER; + bf.BlendFlags = 0; + + // fortunately blending this is easy; we don't have to touch the pixels at all because BLENDFUNCTION has a way to take care of this... + bf.SourceConstantAlpha = c->a; + bf.AlphaFormat = 0; // no per-pixel apha + // a consequence of this setup is that the image data doesn't need to be alpha-premultiplied, because SourceConstantAlpha will be multiplied for us (according to the docs, anyway) + // TODO is this actually the case? + + // and we can finally alpha-blend this! + if (AlphaBlend(c->dc, a->r.left, a->r.top, + a->r.right - a->r.left, a->r.bottom - a->r.top, + a->dc, 0, 0, + a->r.right - a->r.left, a->r.bottom - a->r.top, + bf) == FALSE) + logLastError("error alpha blending in finishAlpha()"); + + // and clean it all up + if (SelectObject(a->dc, a->prevbitmap) != a->bitmap) + logLastError("error deselecting bitmap in finishAlpha()"); + if (DeleteObject(a->bitmap) == 0) + logLastError("error deleting object in finishAlpha()"); + if (DeleteDC(a->dc) == 0) + logLastError("error deleting DC in finishAlpha()"); +} + +void uiDrawStroke(uiDrawContext *c, uiDrawStrokeParams *p) +{ + HPEN pen, prevpen; + HDC strokeDC; + struct alpha alpha; + + if (EndPath(c->dc) == 0) + logLastError("error ending path in uiDrawStroke()"); + + if (c->useAlpha) { + startAlpha(c, &alpha); + strokeDC = alpha.dc; + } else // no alpha; just stroke directly + strokeDC = c->dc; + + pen = toPen(c, p); + if (p->Join == uiDrawLineJoinMiter) + if (SetMiterLimit(strokeDC, p->MiterLimit, NULL) == 0) + logLastError("error setting miter limit in uiDrawStroke()"); + prevpen = SelectObject(strokeDC, pen); + if (prevpen == NULL) + logLastError("error selecting pen into DC in uiDrawStroke()"); + if (StrokePath(strokeDC) == 0) + logLastError("error stroking path in uiDrawStroke()"); + if (SelectObject(strokeDC, prevpen) != pen) + logLastError("error deselecting pen from DC in uiDrawStroke()"); + if (DeleteObject(pen) == 0) + logLastError("error deleting pen in uiDrawStroke()"); + + if (c->useAlpha) + finishAlpha(c, &alpha); +} + +static HBRUSH toBrush(uiDrawContext *c) +{ + HBRUSH brush; + + if (c->useRGB) { + brush = CreateSolidBrush(RGB(c->r, c->g, c->b)); + if (brush == NULL) + logLastError("error creating solid brush in toBrush()"); + return brush; + } + // TODO } void uiDrawFill(uiDrawContext *c, uiDrawFillMode mode) { - // TODO + HBRUSH brush, prevbrush; + HDC fillDC; + struct alpha alpha; + int pfm; + + if (EndPath(c->dc) == 0) + logLastError("error ending path in uiDrawFill()"); + + if (c->useAlpha) { + startAlpha(c, &alpha); + fillDC = alpha.dc; + } else // no alpha; just stroke directly + fillDC = c->dc; switch (mode) { case uiDrawFillModeWinding: + pfm = WINDING; break; case uiDrawFillModeAlternate: + pfm = ALTERNATE; break; } + if (SetPolyFillMode(fillDC, pfm) == 0) + logLastError("error setting fill mode in uiDrawFill()"); + brush = toBrush(c); + prevbrush = SelectObject(fillDC, brush); + if (prevbrush == NULL) + logLastError("error selecting brush into DC in uiDrawFill()"); + if (FillPath(fillDC) == 0) + logLastError("error filling path in uiDrawFill()"); + if (SelectObject(strokeDC, prevbrush) != brush) + logLastError("error deselecting brush from DC in uiDrawFill()"); + if (DeleteObject(brush) == 0) + logLastError("error deleting pen in uiDrawStroke()"); + + if (c->useAlpha) + finishAlpha(c, &alpha); } diff --git a/winarea/ui.h b/winarea/ui.h index 77236099..8a77ae9c 100644 --- a/winarea/ui.h +++ b/winarea/ui.h @@ -56,7 +56,6 @@ enum uiDrawLineJoin { #define uiDrawDefaultMiterLimit 10.0 // TODOs -// - windows: SetPolyFillMode // - os x: FillPath/EOFillPath functions enum uiDrawFillMode { uiDrawFillModeWinding,