I am working on a Path drawing tool and made a previous post about how I could achieve smoother drawing. I have done some tinkering with cubicTo()
and quadTo()
but these have really not been much help. I don’t know if my math is correct, or how I would even calculate a smoother drawn path. Someone on my last post recommended a series of math formulas (polynomials) to achieve a smooth path. Any code snippets are appreciated.
def on_path_draw_start(self, event):
# Check the button being pressed
if event.button() == Qt.LeftButton:
# Create a new path
self.path = QPainterPath()
self.path.moveTo(self.mapToScene(event.pos()))
self.last_point = self.mapToScene(event.pos())
# Set drag mode
self.setDragMode(QGraphicsView.NoDrag)
def on_path_draw(self, event):
# Check the buttons
if event.buttons() == Qt.LeftButton:
# Calculate average point for smoother curve
mid_point = (self.last_point + self.mapToScene(event.pos())) / 2.0
# Use the mid_point as control point for quadTo
self.path.quadTo(self.last_point, mid_point)
self.last_point = self.mapToScene(event.pos())
# Remove temporary path if it exists
if self.temp_path_item:
self.canvas.removeItem(self.temp_path_item)
# Load temporary path as QGraphicsItem to view it while drawing
self.temp_path_item = CustomPathItem(self.path)
self.temp_path_item.setPen(self.pen)
if self.button3.isChecked():
self.temp_path_item.setBrush(QBrush(QColor(self.stroke_fill_color)))
self.temp_path_item.setZValue(2)
self.canvas.addItem(self.temp_path_item)
# Create a custom tooltip for the current coords
scene_pos = self.mapToScene(event.pos())
QToolTip.showText(event.pos(), f'dx: {round(scene_pos.x(), 1)}, dy: {round(scene_pos.y(), 1)}')
self.canvas.update()
def on_path_draw_end(self, event):
# Check the buttons
if event.button() == Qt.LeftButton:
# Calculate average point for smoother curve
mid_point = (self.last_point + self.mapToScene(event.pos())) / 2.0
# Use the mid_point as control point for quadTo
self.path.quadTo(self.last_point, mid_point)
self.last_point = self.mapToScene(event.pos())
# Check if there is a temporary path (if so, remove it now)
if self.temp_path_item:
self.canvas.removeItem(self.temp_path_item)
# If stroke fill button is checked, close the subpath
if self.button3.isChecked():
self.path.closeSubpath()
self.canvas.update()
# Load main path as QGraphicsItem
path_item = CustomPathItem(self.path)
path_item.setPen(self.pen)
path_item.setZValue(0)
# If stroke fill button is checked, set the brush
if self.button3.isChecked():
path_item.setBrush(QBrush(QColor(self.stroke_fill_color)))
# Add item
self.canvas.addItem(path_item)
# Set Flags
path_item.setFlag(QGraphicsItem.ItemIsSelectable)
path_item.setFlag(QGraphicsItem.ItemIsMovable)
# Set Tooltop
path_item.setToolTip('MPRUN Path Element')
I understand this is a huge undertaking, but I need some guidance. I am not a math wizard by any means. Or maybe I don’t even need math, I really don’t know.
I basically just implemented a smoothing algorithm with Numpy and SciPy. Here's the code:
def smooth_path(self, path):
vertices = [(point.x(), point.y()) for point in path.toSubpathPolygons()[0]]
x, y = zip(*vertices)
tck, u = splprep([x, y], s=0)
smooth_x, smooth_y = splev(np.linspace(0, 1, len(vertices) * 2), tck) # Reduce the granularity
smoothed_vertices = np.column_stack((smooth_x, smooth_y))
simplified_vertices = approximate_polygon(smoothed_vertices, tolerance=2.0) # Adjust the tolerance as needed
smooth_path = QPainterPath()
smooth_path.moveTo(simplified_vertices[0][0], simplified_vertices[0][1])
for i in range(1, len(simplified_vertices) - 2, 3):
smooth_path.cubicTo(
simplified_vertices[i][0], simplified_vertices[i][1],
simplified_vertices[i + 1][0], simplified_vertices[i + 1][1],
simplified_vertices[i + 2][0], simplified_vertices[i + 2][1]
)
return smooth_path
To make this work, you just use the function:
my_graphics_path_item.smooth_path(my_graphics_path_item.path())
And you add the item to the scene, and so on...
You can also change the tolerance of the smoothness (I found 2.0 works best)
I hope this helps anybody with this peculiar problem.