4 votes

Chemin d'accès Graphic bizarrement dessiné avec Graphics.FillPath

J'ai écrit un code qui crée un rectangle arrondi GraphicsPath, basé sur une structure personnalisée, BorderRadius (qui me permet de définir le rayon du coin supérieur gauche, supérieur droit, inférieur gauche et inférieur droit du rectangle), et le Rectangle initial lui-même :

public static GraphicsPath CreateRoundRectanglePath(BorderRadius radius, Rectangle rectangle)
{
    GraphicsPath result = new GraphicsPath();

    if (radius.TopLeft > 0)
    {
        result.AddArc(rectangle.X, rectangle.Y, radius.TopLeft, radius.TopLeft, 180, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y), new System.Drawing.Point(rectangle.X, rectangle.Y));
    }
    if (radius.TopRight > 0)
    {
        result.AddArc(rectangle.X + rectangle.Width - radius.TopRight, rectangle.Y, radius.TopRight, radius.TopRight, 270, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y));
    }
    if (radius.BottomRight > 0)
    {
        result.AddArc(rectangle.X + rectangle.Width - radius.BottomRight, rectangle.Y + rectangle.Height - radius.BottomRight, radius.BottomRight, radius.BottomRight, 0, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height));
    }
    if (radius.BottomLeft > 0)
    {
        result.AddArc(rectangle.X, rectangle.Y + rectangle.Height - radius.BottomLeft, radius.BottomLeft, radius.BottomLeft, 90, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height));
    }

    return result;
}

Maintenant, lorsque j'utilise cela avec FillPath et DrawPath, je remarque des résultats étranges :

GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), new Rectangle(10, 10, 100, 100));
e.Graphics.DrawPath(new Pen(Color.Black, 1), path);
e.Graphics.FillPath(new SolidBrush(Color.Black), path);

J'ai zoomé sur chaque Rectangle résultant (à droite) pour que vous puissiez voir clairement, le problème :

rectangles

Ce que je voudrais savoir, c'est : Pourquoi tous les arcs du rectangle dessiné sont-ils égaux, et pourquoi tous les arcs du rectangle rempli sont-ils étranges ?

Mieux encore, est-il possible de corriger cela pour que le rectangle rempli soit dessiné correctement ?

EDIT : Est-il possible de remplir l'intérieur d'un GraphicsPath sans utiliser FillPath ?

EDIT : Comme indiqué dans les commentaires....voici un exemple de la structure BorderRadius

public struct BorderRadius
{
    public Int32 TopLeft { get; set; }
    public Int32 TopRight { get; set; }
    public Int32 BottomLeft { get; set; }
    public Int32 BottomRight { get; set; }

    public BorderRadius(int all) : this()
    {
        this.TopLeft = this.TopRight = this.BottomLeft = this.BottomRight = all;
    }
}

4voto

gigi Points 322

J'ai rencontré le même problème et trouvé une solution. Il est peut-être trop tard pour vous @seriesOne mais cela peut être utile à d'autres personnes si elles rencontrent ce problème. Essentiellement, lors de l'utilisation des méthodes de remplissage (et également lors de la définition du rectangle arrondi comme chemin de découpe avec Graphics.SetClip), nous devons déplacer d'un pixel les lignes de droite et du bas. J'ai donc mis au point une méthode qui accepte un paramètre pour corriger le rectangle en fonction de l'utilisation du remplissage ou non. La voici :

private static GraphicsPath CreateRoundedRectangle(Rectangle b, int r, bool fill = false)
{
    var path = new GraphicsPath();
    var r2 = (int)r / 2;
    var fix = fill ? 1 : 0;

    b.Location = new Point(b.X - 1, b.Y - 1);
    if (!fill)
        b.Size = new Size(b.Width - 1, b.Height - 1);

    path.AddArc(b.Left, b.Top, r, r, 180, 90);
    path.AddLine(b.Left + r2, b.Top, b.Right - r2 - fix, b.Top);

    path.AddArc(b.Right - r - fix, b.Top, r, r, 270, 90);
    path.AddLine(b.Right, b.Top + r2, b.Right, b.Bottom - r2);

    path.AddArc(b.Right - r - fix, b.Bottom - r - fix, r, r, 0, 90);
    path.AddLine(b.Right - r2, b.Bottom, b.Left + r2, b.Bottom);

    path.AddArc(b.Left, b.Bottom - r - fix, r, r, 90, 90);
    path.AddLine(b.Left, b.Bottom - r2, b.Left, b.Top + r2);

    return path;
}

Voici comment l'utiliser :

g.DrawPath(new Pen(Color.Red), CreateRoundedRectangle(rect, 24, false));
g.FillPath(new SolidBrush(Color.Red), CreateRoundedRectangle(rect, 24, true));

1voto

Vincent Povirk Points 1487

Je suggérerais d'ajouter explicitement une ligne de la fin de chaque arc au début du suivant.

Vous pourriez également essayer d'utiliser la méthode Flatten pour approximer toutes les courbes dans votre chemin avec des lignes. Cela devrait supprimer toute ambiguïté.

Le résultat que vous obtenez de FillPath ressemble à un problème que j'ai rencontré où les points sur un chemin étaient interprétés de manière incorrecte, conduisant essentiellement à une spline de Bézier quadratique au lieu d'une cubique.

Vous pouvez examiner les points sur votre chemin en utilisant la fonction GetPathData: http://msdn.microsoft.com/en-us/library/ms535534%28v=vs.85%29.aspx

Les courbes de Bézier (que GDI+ utilise pour approximer les arcs) sont représentées par 4 points. Le premier est un point d'extrémité et peut être de n'importe quel type. Le deuxième et le troisième sont les points de contrôle et ont le type PathPointBezier. Le dernier est l'autre point d'extrémité et a le type PathPointBezier. En d'autres termes, lorsque GDI+ voit PathPointBezier, il utilise la position actuelle du chemin et les 3 points de Bézier pour dessiner la courbe. Les courbes de Bézier peuvent être enchaînées, mais le nombre de points de Bézier doit toujours être divisible par 3.

Ce que vous faites est un peu étrange, car vous dessinez des courbes à différents endroits sans lignes explicites pour les joindre. Je suppose que cela crée un schéma comme celui-ci:

PathPointStart - point d'extrémité du premier arc
PathPointBezier - point de contrôle du premier arc
PathPointBezier - point de contrôle du premier arc
PathPointBezier - point d'extrémité du premier arc
PathPointLine - point d'extrémité du deuxième arc
PathPointBezier - point de contrôle du deuxième arc
PathPointBezier - point de contrôle du deuxième arc
PathPointBezier - point d'extrémité du deuxième arc
PathPointLine - point d'extrémité du troisième arc
PathPointBezier - point de contrôle du troisième arc
PathPointBezier - point de contrôle du troisième arc
PathPointBezier - point d'extrémité du troisième arc

Cela semble raisonnable. GDI+ devrait dessiner une ligne depuis le dernier point d'extrémité de chaque courbe jusqu'au premier point d'extrémité de la suivante. DrawPath le fait clairement, mais je pense que FillPath interprète les points différemment. Certains points d'extrémité sont traités comme des points de contrôle et vice versa.

1voto

Filip Navara Points 3679

La vraie raison du comportement est expliquée sur le comportement des pixels de FillRectangle et DrawRectangle.

Cela a à voir avec l'arrondi des pixels par défaut et le fait que FillRectangle/FillPath avec des coordonnées entières finissent par dessiner au milieu d'un pixel et sont arrondis (selon Graphics.PixelOffsetMode).

D'autre part, DrawRectangle/DrawPath dessinent avec un stylo de 1px qui est parfaitement arrondi sur les limites des pixels.

En fonction de votre utilisation, la solution pourrait être d'infléchir/dégonfler le rectangle pour FillRectangle/FillPath de .5px.

0voto

Idle_Mind Points 8513

"Est-il possible de remplir l'intérieur d'un GraphicsPath sans utiliser FillPath?"

Oui... mais je pense que c'est plus un tour de passe-passe (et cela pourrait ne pas fonctionner comme prévu pour des formes plus complexes). Vous pouvez découper les graphiques selon le chemin, puis simplement remplir le rectangle englobant entièrement :

        Rectangle rc = new Rectangle(10, 10, 100, 100);
        GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), rc);
        e.Graphics.SetClip(path);
        e.Graphics.FillRectangle(Brushes.Black, rc);
        e.Graphics.ResetClip();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X