What is wrong with Path.CURVE3 and Path.CURVE4? Bézier curve messes up in a certain point

Question:

I want to draw Bézier curves on JupyterLab with Python, and I am using MatplotLib in order to make a replica of a Lamborghini. The drawing has to be a faithful replica, so I used NumPy to bring the Lamborghini image as a background, as long as I can draw over it and manipulate its properties such as opacity. But no matter what I do, these very first points of the bumber just go crazy. I tried using Path.CURVE3 and Path.CURVE4, Path.STOP and also Path.LINETO in order to make the Bézier curve stop properly on a given point (curve going crazy). I also tried making smaller curves, but I am not able to see what I want. I have to follow exactly the curves of the car. I used GIMP to extract the points I need.

Here is my code, actually, this situation is even more common than what I exposed here, it happens without NumPy image processing too. What are the rules of using CURVE objects? What are the limitations? What can make my curves go wrongly crazy?

import math
import matplotlib.patches as mpatches
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import numpy as np

Path = mpath.Path

fig, ax = plt.subplots()

bumper = mpatches.PathPatch(
    Path(
        [(22, 163), (12, 134), (13, 132), (30.6, 117.65), (43, 118)],
        [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4],
    ),
    fc="none",
    transform=ax.transData,
    color="green",
    lw=1,
)

ax.add_patch(bumper)

ax.set_title("Lamborghini Sián FKP 37", fontfamily="Tahoma", fontsize=22)
ax.set_aspect(1)

plt.xticks(np.linspace(0, 1180, 21), fontsize=10)
plt.yticks(np.linspace(0, 330, 9), fontsize=10)
plt.grid(color="gray", ls=":", lw=0.2)

lambo = plt.imread("Lamborghini Sian FKP 37 Right Side.png")

plt.imshow(lambo, alpha=0.35, extent=[0, 1180, 0, 330])

plt.rcParams["figure.figsize"] = (15, 6)

plt.show()
Asked By: NickS1

||

Answers:

Research and documentation

So, after a period of research, I found this in MatplotLib’s documentation. Which made me figure out the meaning of quadratic CURVE3 and cubic CURVE4 curves from `Path.

CURVE3 means you can only plot a curve with 2 given points, and CURVE4 with 3 points. We have to keep in mind that:

Start Point is like putting the pencil on the paper, the very first point that begins a new trajectory. You achieve this by starting with Path.MOVETO;

Control Point is what really makes the curve. You can think of a perfectly straight rope between 2 given points. When the rope is pulled in some direction, it bends the rope creating the curve;

End Point is the very last point where you stop drawing and take the pencil off the paper.


How to use CURVE attributes:

Path.CURVE3 must be used with exactly 2 points:

  • 1 Control Point;
  • 1 End Point.

Path.CURVE4 must be used with exact 3 points:

  • 2 Control Points;
  • 1 End Point.

I was struggling in a situation similar to this one:

# I had to put a lot of points in a `verts` list like this
verts = [(x,y), (x,y), (x,y), (x,y), (x,y)]

# Following the previous path structure, I got a `codes` list like this
codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CURVE4]

Note that in this example we have 5 points. When we are going to plot Bézier curves, we have to analyze whether it has to be a quadratic CURVE3 or cubic CURVE4 curve.

If you have 4 points, use CURVE4, if 3 use CURVE3. It is up to you on how you are going to organize a bigger figure in subdivided curves, you may compose a plot from various different curves.

If you have more than the limit of points, you have to restart the path regarding the size of the curve. Like this:

verts = [(5, 30), (15, 55), (25, 15), (35, 40),  # 4 points
         (20, 10), (30, 0), (35, 10)],           # 3 new points, start a new curve

# `codes` means: the instructions used to guide the line through the points
codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, # Begin the curve
         Path.MOVETO, Path.CURVE3, Path.CURVE3]              # Start a new one

bezier1 = mpatches.PathPatch(
    Path(verts, codes),
    # You can also tweak your line properties, like its color, width, etc.
    fc="none", transform=ax.transData, color="green", lw=2)

plot example

IMPORTANT: if you are plotting a single long curve, when a part of the line is completed, you have to start the continuation of the line by the immediate last point, this will give you a continuous curve. That is what I discovered, I hope this helps someone.

Answered By: NickS1