[2/2] gdiplus: added linecap rendering for GdipDrawBezier

Evan Stade estade at gmail.com
Fri Jul 6 18:13:49 CDT 2007


Hi,

This patch is somewhat trickier than part 1 of this series.  It is
smaller because it does not include the linecap-rendering code but it
has the sensitive shorten_bezier_amt function, which changes a series
of bezier points to render the same bezier curve, but with the
endpoint a little shorter.  This is necessary for the
LineCapArrowAnchor style end cap.

Changelog:
* added end cap drawing for bezier curves (similar to last patch,
which added them for straight lines)

 dlls/gdiplus/graphics.c |   89 ++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 83 insertions(+), 6 deletions(-)

-- 
Evan Stade
-------------- next part --------------
diff --git a/dlls/gdiplus/graphics.c b/dlls/gdiplus/graphics.c
index 6c40aec..e8369ac 100644
--- a/dlls/gdiplus/graphics.c
+++ b/dlls/gdiplus/graphics.c
@@ -32,6 +32,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
 /* looks-right constants */
 #define TENSION_CONST (0.3)
 #define ANCHOR_WIDTH (2.0)
+#define MAX_ITERS (50)
 
 static inline INT roundr(REAL x)
 {
@@ -317,6 +318,72 @@ static void draw_polyline(HDC hdc, GpPen
     GdipFree(pti);
 }
 
+/* Conducts a linear search to find the bezier points that will back off
+ * the endpoint of the curve by a distance of amt. Linear search works
+ * better than binary in this case because there are multiple solutions,
+ * and binary searches often find a bad one. I don't think this is what
+ * Windows does but short of rendering the bezier without GDI's help it's
+ * the best we can do. */
+static void shorten_bezier_amt(GpPointF * pt, REAL amt)
+{
+    GpPointF origpt[4];
+    REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0;
+    INT i;
+
+    memcpy(origpt, pt, sizeof(GpPointF) * 4);
+
+    for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
+        /* reset bezier points to original values */
+        memcpy(pt, origpt, sizeof(GpPointF) * 4);
+        /* Perform magic on bezier points. Order is important here.*/
+        shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
+        shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
+        shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
+        shorten_line_percent(pt[0].X, pt[0].Y, &pt[1].X, &pt[1].Y, percent);
+        shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
+        shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
+
+        dx = pt[3].X - origx;
+        dy = pt[3].Y - origy;
+
+        diff = sqrt(dx * dx + dy * dy);
+        percent += 0.0005 * amt;
+    }
+}
+
+/* Draws bezier curves between given points, and if caps is true then draws an
+ * endcap at the end of the last line.  FIXME: Startcaps not implemented. */
+static void draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
+    INT count, BOOL caps)
+{
+    POINT *pti = GdipAlloc(count * sizeof(POINT));
+    GpPointF *ptf = GdipAlloc(4 * sizeof(GpPointF));
+    INT i;
+
+    memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF));
+
+    if(caps){
+        if(pen->endcap == LineCapArrowAnchor)
+            shorten_bezier_amt(ptf, pen->width);
+
+        draw_cap(hdc, pen->color, pen->endcap, pen->width, ptf[3].X,
+            ptf[3].Y, pt[count - 1].X, pt[count - 1].Y);
+    }
+
+    for(i = 0; i < count - 4; i ++){
+        pti[i].x = roundr(pt[i].X);
+        pti[i].y = roundr(pt[i].Y);
+    }
+    for(i = 0; i < 4; i ++){
+        pti[i].x = roundr(ptf[i].X);
+        pti[i].y = roundr(ptf[i].Y);
+    }
+
+    PolyBezier(hdc, pti, count);
+    GdipFree(pti);
+    GdipFree(ptf);
+}
+
 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
 {
     if(hdc == NULL)
@@ -385,18 +452,28 @@ GpStatus WINGDIPAPI GdipDrawArc(GpGraphi
 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
     REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
 {
-    HGDIOBJ old_pen;
-    POINT pt[4] = {{roundr(x1), roundr(y1)}, {roundr(x2), roundr(y2)},
-                   {roundr(x3), roundr(y3)}, {roundr(x4), roundr(y4)}};
+    INT save_state;
+    GpPointF pt[4];
 
     if(!graphics || !pen)
         return InvalidParameter;
 
-    old_pen = SelectObject(graphics->hdc, pen->gdipen);
+    pt[0].X = x1;
+    pt[0].Y = y1;
+    pt[1].X = x2;
+    pt[1].Y = y2;
+    pt[2].X = x3;
+    pt[2].Y = y3;
+    pt[3].X = x4;
+    pt[3].Y = y4;
 
-    PolyBezier(graphics->hdc, pt, 4);
+    save_state = SaveDC(graphics->hdc);
+    EndPath(graphics->hdc);
+    SelectObject(graphics->hdc, pen->gdipen);
 
-    SelectObject(graphics->hdc, old_pen);
+    draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
+
+    RestoreDC(graphics->hdc, save_state);
 
     return Ok;
 }
-- 
1.4.1


More information about the wine-patches mailing list