I was trying to make a nurbs curve with the qt painter or with some class alike. But I can’t seem to find evidence of it being possible. Is it really missing or there is a way to do it and I am not finding it?
Hi
what is a nurb curve?
you can draw bezier curves, but nurbs, I don’t know
but if you have mathematics knowledge about it, calculate points and draw a path.
Grum999
A nurbs curve are like a bezier curves but without handles. It uses the other points nearby to sorta act like handles themselves.
You can make circles easy with just 4 points if they are all at the same distance per example. Bezier curves would need editing the handles also which would make things harder for the user I think though having much more control on the curve.
Ussualy you have nurbs and bezier side by side.
It looks kinda like this
I don’t have knowledge how to do that mathematically, at least not yet. But you can use a function to plot a path? Just like that? I haven’t tackled beziers yet because I have had no need yet. I just expected nurbs around considering the existence of beziers.
with Qt, you can use QPainterPath
It’s easy to use, you can combine lines, arcs, bézier… in a path
Here an example I made for newspaper plugin
- create path
path = QPainterPath(QPointF(0, outputHeight/2))
path.cubicTo(QPointF(outputWidth * 0.125, outputHeight * 0.25), QPointF(outputWidth * 0.375, outputHeight * 0.25), QPointF(outputWidth/2, outputHeight/2))
path.cubicTo(QPointF(outputWidth * 0.625, outputHeight * 0.75), QPointF(outputWidth * 0.875, outputHeight * 0.75), QPointF(outputWidth, outputHeight/2))
path.cubicTo(QPointF(outputWidth * 0.625, outputHeight * 0.65), QPointF(outputWidth * 0.875, outputHeight * 0.65), QPointF(outputWidth/2, outputHeight/2))
path.cubicTo(QPointF(outputWidth * 0.125, outputHeight * 0.35), QPointF(outputWidth * 0.375, outputHeight * 0.35), QPointF(0, outputHeight/2))
path.closeSubpath()
- draw/fil path on painter
canvas = QPainter()
canvas.begin(self.__pixmapStylePreviewCMYK[currentMode])
canvas.fillRect(QRectF(0,0,outputWidth, outputHeight), gradient);
canvas.setBrush(QBrush(QColor(Qt.black), Qt.SolidPattern))
canvas.setPen(QPen(Qt.NoPen))
canvas.setRenderHint(QPainter.Antialiasing, True)
canvas.fillPath(path, QBrush(QColor(Qt.black), Qt.SolidPattern))
canvas.end()
In composition helper plgin, I also use path to build the spiral helper.
After, you need to understand how arc or bezier works with Qt.
But it’s not difficult, just do some simple tests to understand how to use it, but documentation is clear and contain examples.
The most simple is to start with simple lines 
Grum999
was taking a looking at math about it.
give me a bit to digest that code, and the other functions of that class.
The video provides very good and simple explanation about what is a quadratic curve and a bézier curve.
But with provided Qt function I don’t think you need to calculate curve points by yourself.
Except if you want to use it to define a setting curve (like Color Adjustment Curve filter), In this case, use it curve for rendering it, and keep mathematical calculation of point in curve only for data processing.
Grum999
Was doing some crazy experiments with it:
like it does the circles but they look a bit wonky
def paintEvent(self, event):
# Start Qpainter
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
# Pen and Brush
painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
painter.setBrush(QBrush(QColor(190,53,67)))
# Painter Path
path = QPainterPath(QPoint(100,200))
# Cubic Method
path.cubicTo(100,200,100,100,200,100)
path.cubicTo(200,100,300,100,300,200)
path.cubicTo(300,200,300,300,200,300)
path.cubicTo(200,300,100,300,100,200)
# Quad Method
path.moveTo(400, 200)
path.quadTo(400,100, 500,100)
path.quadTo(600,100, 600,200)
path.quadTo(600,300, 500,300)
path.quadTo(400,300, 400,200)
painter.drawPath(path)
# Finish QPainter
painter.end()
self.update()
it does closer what I wanted as a nurbs behaviour but would still be off. I guess the only better option is to make a draw ellipse but for what I wanted I dont think that flies. the ideal scenario would be a circle with 4 control points only as Nurbs do. I will keep exploring this class there must be a way.
For the cubic segments you are putting the first control point in the same place as the first point.
A circle can only be approximated with cubic bezier curves. You can try:
path.cubicTo(100,150,150,100,200,100)
path.cubicTo(250,100,300,150,300,200)
path.cubicTo(300,250,250,300,200,300)
path.cubicTo(150,300,100,250,100,200)
Here you can find more info and a more precise offset for the control points:
http://www.whizkidtech.redprince.net/bezier/circle/
@Deif_Lou Sadley it becomes a bit wonky too.

I was reading information on nurbs curves in math books now to try and see if I can understand any of it.
I think it might be a better choice for my case to just make a for loop and create a poly path with a variable level of detail for it’s size. that or I just hardcode the interpolation values that make up a circle and are dependant of the plane control points.
What are you trying to achieve?
Yeah, you can not make a perfect circle with cubic splines, you can only approximate it.
Have you tried the values for the control points suggested in the links I posted? They are the ones that better approximate the circle with a 4 point cubic bezier spline. These are images taken from that stackoverflow thread. As you can see in the second image, the approximation is almost perfect:
Yes I did pass through that one too. but what i made from adjusting those values only remade what I already did before the results were not that great.
Unless I tottally used it all wrong… that is also a possibility I am still getting the hang of this class.
As for my objective, in Blender I can show the NURBs curves in action while in edit mode.
I want to set up a circle made up with 4 control points making up a circle (does not need to be super perfect just not too wonky) so I distort it in the way shown in the gif.

you can take a look on what I have now too (still wip). I have a Polygon triangle and a square reacting in such a way but they have no curvy curves. I was trying to make the Circle happen next or perhaps a tear shape object but reacting in the same sort of fashion.

my testing that quadTo does not work for my case, it gets or pointy or too squary. This happens because the control point is shared:

# Default Values
self.event_x = 0
self.event_y = 0
def mousePressEvent(self, event):
self.Mouse(event)
def mouseMoveEvent(self, event):
self.Mouse(event)
def Mouse(self, event):
self.event_x = event.x()
self.event_y = event.y()
def paintEvent(self, event):
# Start Qpainter
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
# Pen and Brush
painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
painter.setBrush(QBrush(QColor(190,53,67)))
# Painter Path
path = QPainterPath()
# # Cubic
# path.cubicTo(100,200,100,100,200,100)
# path.cubicTo(200,100,300,100,300,200)
# path.cubicTo(300,200,300,300,200,300)
# path.cubicTo(200,300,100,300,100,200)
# # Quad
# path.moveTo(400, 200)
# path.quadTo(400,100, 500,100)
# path.quadTo(600,100, 600,200)
# path.quadTo(600,300, 500,300)
# path.quadTo(400,300, 400,200)
# # Deif_Lou
# path.moveTo(100,200)
# path.cubicTo(100,150,150,100,200,100)
# path.cubicTo(250,100,300,150,300,200)
# path.cubicTo(300,250,250,300,200,300)
# path.cubicTo(150,300,100,250,100,200)
# Quad
P1 = [100,100]
P2 = [500,100]
P3 = [500,500]
P4 = [100,500]
PM = self.Math_2D_Centroid_Square(P1[0],P1[1],P2[0],P2[1],P3[0],P3[1],P4[0],P4[1])
dist_x = self.event_x - PM[0]
dist_y = self.event_y - PM[1]
P12 = [self.LERP(P1[0],P2[0],0.5), self.LERP(P1[1],P2[1],0.5)]
P23 = [self.LERP(P2[0],P3[0],0.5), self.LERP(P2[1],P3[1],0.5)]
P34 = [self.LERP(P3[0],P4[0],0.5), self.LERP(P3[1],P4[1],0.5)]
P41 = [self.LERP(P4[0],P1[0],0.5), self.LERP(P4[1],P1[1],0.5)]
# path.moveTo(P41[0],P41[1])
# path.quadTo(P1[0],P1[1], P12[0],P12[1])
# path.quadTo(P2[0],P2[1], P23[0],P23[1])
# path.quadTo(P3[0],P3[1], P34[0],P34[1])
# path.quadTo(P4[0],P4[1], P41[0],P41[1])
path.moveTo(P41[0],P41[1])
path.quadTo(PM[0]-dist_x,PM[1]+dist_y, P12[0],P12[1])
path.quadTo(self.event_x,self.event_y, P23[0],P23[1])
path.quadTo(PM[0]+dist_x,PM[1]-dist_y, P34[0],P34[1])
path.quadTo(PM[0]-dist_x,PM[1]-dist_y, P41[0],P41[1])
painter.drawPath(path)
painter.drawLine(0,self.event_y, 1000,self.event_y)
painter.drawLine(self.event_x,0, self.event_x,1000)
painter.drawLine(P2[0],P2[1], P4[0],P4[1])
# Finish QPainter
painter.end()
self.update()
self.layout.label_1.setText(str(self.event_x))
self.layout.label_2.setText(str(self.event_y))
def LERP(self, v0, v1, t):
return (v0+t*(v1-v0))
def Math_2D_Centroid_Square(self, a1, a2, b1, b2, c1, c2, d1, d2):
cx = (a1+b1+c1+d1)/4
cy = (a2+b2+c2+d2)/4
return [cx, cy]
I am gonna play with cubicTo next.
So I was doing the same for cubicTo and I found my mistake it was in the order of input and their meanning for the bezier curve plotting. Now I should be able to use the value you sent me @Deif_Lou

This one does little lotus flowers if you want to click on it for fun
# Default Values
self.event_x = 0
self.event_y = 0
def mousePressEvent(self, event):
self.Mouse(event)
def mouseMoveEvent(self, event):
self.Mouse(event)
def Mouse(self, event):
self.event_x = event.x()
self.event_y = event.y()
def paintEvent(self, event):
# Start Qpainter
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
# Pen and Brush
painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
painter.setBrush(QBrush(QColor(190,53,67)))
# Painter Path
path = QPainterPath()
# # Cubic
# path.cubicTo(100,200,100,100,200,100)
# path.cubicTo(200,100,300,100,300,200)
# path.cubicTo(300,200,300,300,200,300)
# path.cubicTo(200,300,100,300,100,200)
# # Quad
# path.moveTo(400, 200)
# path.quadTo(400,100, 500,100)
# path.quadTo(600,100, 600,200)
# path.quadTo(600,300, 500,300)
# path.quadTo(400,300, 400,200)
# # Deif_Lou
# path.moveTo(100,200)
# path.cubicTo(100,150,150,100,200,100)
# path.cubicTo(250,100,300,150,300,200)
# path.cubicTo(300,250,250,300,200,300)
# path.cubicTo(150,300,100,250,100,200)
# Quad
P1 = [100,100]
P2 = [500,100]
P3 = [500,500]
P4 = [100,500]
P12 = [self.LERP(P1[0],P2[0],0.5), self.LERP(P1[1],P2[1],0.5)]
P23 = [self.LERP(P2[0],P3[0],0.5), self.LERP(P2[1],P3[1],0.5)]
P34 = [self.LERP(P3[0],P4[0],0.5), self.LERP(P3[1],P4[1],0.5)]
P41 = [self.LERP(P4[0],P1[0],0.5), self.LERP(P4[1],P1[1],0.5)]
dist_x = self.event_x - P41[0]
dist_y = self.event_y - P41[1]
path.moveTo(P41[0],P41[1])
path.cubicTo(self.event_x,self.event_y, P12[0]+dist_y,P12[1]+dist_x, P12[0],P12[1])
path.cubicTo(P12[0]-dist_y,P12[1]+dist_x, P23[0]-dist_x,P23[1]+dist_y, P23[0],P23[1])
path.cubicTo(P23[0]-dist_x,P23[1]-dist_y, P34[0]-dist_y,P34[1]-dist_x, P34[0],P34[1])
path.cubicTo(P34[0]+dist_y,P34[1]-dist_x, P41[0]+dist_x,P41[1]-dist_y, P41[0],P41[1])
painter.drawPath(path)
painter.drawLine(0,self.event_y, 1000,self.event_y)
painter.drawLine(self.event_x,0, self.event_x,1000)
painter.drawLine(P1[0],P1[1], P3[0],P3[1])
# Finish QPainter
painter.end()
self.update()
self.layout.label_1.setText(str(self.event_x))
self.layout.label_2.setText(str(self.event_y))
def LERP(self, v0, v1, t):
return (v0+t*(v1-v0))
def Math_2D_Centroid_Square(self, a1, a2, b1, b2, c1, c2, d1, d2):
cx = (a1+b1+c1+d1)/4
cy = (a2+b2+c2+d2)/4
return [cx, cy]
But I must say it was fun making an interactive sort of function to check the correct factor for the input.
It was much faster to make than checking the variations of the behavior of a static display.
I’ve made a test. Here you set the corners of a quadrilateral and a cubic bezier spline is fitted inside, choosing the control points in a way such that the spline approximates a circle or a ellipse if the quadrilateral is a square or a rectangle. It doesn’t make perspective correction so sometimes it can appear a bit off visually:
void window::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// lerp function
auto lerp = [](const QPointF &p1, const QPointF &p2, qreal t)
{
return (1.0 - t) * p1 + t * p2;
};
// get bezier spline points from quadrilateral control points
QPointF bezierPoints[4];
bezierPoints[0] = lerp(m_controlPoints[0], m_controlPoints[1], 0.5);
bezierPoints[1] = lerp(m_controlPoints[1], m_controlPoints[2], 0.5);
bezierPoints[2] = lerp(m_controlPoints[2], m_controlPoints[3], 0.5);
bezierPoints[3] = lerp(m_controlPoints[3], m_controlPoints[0], 0.5);
// get bezier spline control points
const qreal optimalDistanceToControlPoint = 0.552284749831;
QPointF bezierControlPoints[4][2];
bezierControlPoints[0][0] = lerp(bezierPoints[0], m_controlPoints[1], optimalDistanceToControlPoint);
bezierControlPoints[0][1] = lerp(bezierPoints[1], m_controlPoints[1], optimalDistanceToControlPoint);
bezierControlPoints[1][0] = lerp(bezierPoints[1], m_controlPoints[2], optimalDistanceToControlPoint);
bezierControlPoints[1][1] = lerp(bezierPoints[2], m_controlPoints[2], optimalDistanceToControlPoint);
bezierControlPoints[2][0] = lerp(bezierPoints[2], m_controlPoints[3], optimalDistanceToControlPoint);
bezierControlPoints[2][1] = lerp(bezierPoints[3], m_controlPoints[3], optimalDistanceToControlPoint);
bezierControlPoints[3][0] = lerp(bezierPoints[3], m_controlPoints[0], optimalDistanceToControlPoint);
bezierControlPoints[3][1] = lerp(bezierPoints[0], m_controlPoints[0], optimalDistanceToControlPoint);
// draw enclosing quadrilateral
painter.setPen(QPen(QColor(0, 0, 0, 128), 1, Qt::DotLine));
painter.drawPolygon(m_controlPoints, 4);
// draw spline
painter.setPen(Qt::red);
painter.setBrush(Qt::NoBrush);
QPainterPath path(bezierPoints[0]);
path.cubicTo(bezierControlPoints[0][0], bezierControlPoints[0][1], bezierPoints[1]);
path.cubicTo(bezierControlPoints[1][0], bezierControlPoints[1][1], bezierPoints[2]);
path.cubicTo(bezierControlPoints[2][0], bezierControlPoints[2][1], bezierPoints[3]);
path.cubicTo(bezierControlPoints[3][0], bezierControlPoints[3][1], bezierPoints[0]);
painter.drawPath(path);
// draw quadrilateral control points
painter.setPen(Qt::black);
painter.setBrush(Qt::white);
for (const QPointF &p: m_controlPoints)
{
painter.drawEllipse(p, 5, 5);
}
// draw spline points
painter.setPen(Qt::black);
painter.setBrush(Qt::white);
for (const QPointF &p: bezierPoints)
{
painter.drawEllipse(p, 2, 2);
}
// draw spline control points
painter.setPen(Qt::black);
painter.setBrush(Qt::white);
for (const QPointF *p : bezierControlPoints)
{
painter.drawEllipse(p[0], 1, 1);
painter.drawEllipse(p[1], 1, 1);
}
}
Another way you can try to make this is by moving the bezier points and computing the tangents and control points like in catmul-rom splines.
uuu that is exactly the sort of effect I am trying to achieve 
I think that distortion is more of a boon than a hinder for the user as you will be able to do some odd selections with it. I have been looking at analog artists gamut masks and they sometimes do some weird shapes to the mask, perspective rules are not required for this case.
So I did the circle as you did but I realized my folly in the concept I had in mind. it made a really good circle but it would not allow me touch the edge of the picker with the node. so I experimented a bit more and on the second version I came up with this variation.

it holds up the shape pretty well until you disarrange the natural order of the nodes making them do arcs into the void. strangely I think still passes no?
Green node is my 1st node, is editable.
Grey are the other nodes, is editable.
Red nodes are construction node, not editable and will be hidden on final version.
what you guys think? @Grum999 @Deif_Lou
if self.shape == "P1_S1":
# Points for User
P1 = [self.P1_S1[0], self.P1_S1[1]]
P2 = [self.P1_S1[2], self.P1_S1[3]]
P3 = [self.P1_S1[4], self.P1_S1[5]]
P4 = [self.P1_S1[6], self.P1_S1[7]]
# Point of the Centroid
# PM = self.Math_2D_Centroid_Square(P1[0],P1[1], P2[0],P2[1], P3[0],P3[1], P4[0],P4[1])
PM = self.Math_2D_Points_Lines_Intersection(P1[0],P1[1], P3[0],P3[1], P2[0],P2[1], P4[0],P4[1])
# # Points Orthogonal Decomposition Considering the Centroid
# dist_P1 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P1[0],P1[1])
# dist_P2 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P2[0],P2[1])
# dist_P3 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P3[0],P3[1])
# dist_P4 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P4[0],P4[1])
# # Bridge Points for the Control Vectors
# P12 = [P1[0]+dist_P2[0], P1[1]+dist_P2[1]]
# P23 = [P2[0]+dist_P3[0], P2[1]+dist_P3[1]]
# P34 = [P3[0]+dist_P4[0], P3[1]+dist_P4[1]]
# P41 = [P4[0]+dist_P1[0], P4[1]+dist_P1[1]]
# Bridge Points
B12 = [self.Math_1D_LERP(P1[0],P2[0],0.5), self.Math_1D_LERP(P1[1],P2[1],0.5)]
B23 = [self.Math_1D_LERP(P2[0],P3[0],0.5), self.Math_1D_LERP(P2[1],P3[1],0.5)]
B34 = [self.Math_1D_LERP(P3[0],P4[0],0.5), self.Math_1D_LERP(P3[1],P4[1],0.5)]
B41 = [self.Math_1D_LERP(P4[0],P1[0],0.5), self.Math_1D_LERP(P4[1],P1[1],0.5)]
# Bridge Components
dist_B12 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B12[0],B12[1])
dist_B23 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B23[0],B23[1])
dist_B34 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B34[0],B34[1])
dist_B41 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B41[0],B41[1])
# Intermediate Points
scalar = 2
P12 = [PM[0]+self.value1*dist_B12[0], PM[1]+self.value1*dist_B12[1]]
P23 = [PM[0]+self.value2*dist_B23[0], PM[1]+self.value2*dist_B23[1]]
P34 = [PM[0]+self.value3*dist_B34[0], PM[1]+self.value3*dist_B34[1]]
P41 = [PM[0]+self.value4*dist_B41[0], PM[1]+self.value4*dist_B41[1]]
# Painter Path Object
path = QPainterPath()
a = 0.551915024494
b = 1 - 0.551915024494
path.moveTo(P1[0], P1[1])
path.cubicTo(
QPoint( self.Math_1D_LERP(P1[0],P12[0],a), self.Math_1D_LERP(P1[1],P12[1],a) ),
QPoint( self.Math_1D_LERP(P12[0],P2[0],b), self.Math_1D_LERP(P12[1],P2[1],b) ),
QPoint(P2[0],P2[1]))
path.cubicTo(
QPoint( self.Math_1D_LERP(P2[0],P23[0],a), self.Math_1D_LERP(P2[1],P23[1],a) ),
QPoint( self.Math_1D_LERP(P23[0],P3[0],b), self.Math_1D_LERP(P23[1],P3[1],b) ),
QPoint(P3[0],P3[1]))
path.cubicTo(
QPoint( self.Math_1D_LERP(P3[0],P34[0],a), self.Math_1D_LERP(P3[1],P34[1],a) ),
QPoint( self.Math_1D_LERP(P34[0],P4[0],b), self.Math_1D_LERP(P34[1],P4[1],b) ),
QPoint(P4[0],P4[1]))
path.cubicTo(
QPoint( self.Math_1D_LERP(P4[0],P41[0],a), self.Math_1D_LERP(P4[1],P41[1],a) ),
QPoint( self.Math_1D_LERP(P41[0],P1[0],b), self.Math_1D_LERP(P41[1],P1[1],b) ),
QPoint(P1[0],P1[1]))
painter.drawPath(path)
# Display Subjective Primaries
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QBrush(QColor(0,150,0)))
painter.drawEllipse(self.P1_S1[0]-self.circle, self.P1_S1[1]-self.circle, self.circle*2,self.circle*2)
painter.setBrush(QBrush(QColor(150,150,150)))
painter.drawEllipse(self.P1_S1[2]-self.circle, self.P1_S1[3]-self.circle, self.circle*2,self.circle*2)
painter.drawEllipse(self.P1_S1[4]-self.circle, self.P1_S1[5]-self.circle, self.circle*2,self.circle*2)
painter.drawEllipse(self.P1_S1[6]-self.circle, self.P1_S1[7]-self.circle, self.circle*2,self.circle*2)
# Display Subjective Neutral
painter.setBrush(QBrush(QColor(150,150,150)))
painter.drawEllipse(self.centroid[0]-self.circle, self.centroid[1]-self.circle, self.circle*2,self.circle*2)
# Intermediate
painter.setBrush(QBrush(QColor(150,0,0)))
painter.drawEllipse(P12[0]-self.circle, P12[1]-self.circle, self.circle*2,self.circle*2)
painter.drawEllipse(P23[0]-self.circle, P23[1]-self.circle, self.circle*2,self.circle*2)
painter.drawEllipse(P34[0]-self.circle, P34[1]-self.circle, self.circle*2,self.circle*2)
painter.drawEllipse(P41[0]-self.circle, P41[1]-self.circle, self.circle*2,self.circle*2)
It looks great although I think it would be nice if it allowed better concave shapes, more rounded.
my first try was this one but it kinda works as an approximation not exact values for the shape but it allows better concaves. I am not sure if it worth the trade off of the fuzzy control gives.

as before the grey nodes are control points and red nodes are circle construction points.
My counter argument was the idea I wanted to place of the points show the color of the location they are as they become the new primary colors of the created gamut as stated by the color theory law XD
For the Triangle and for the Square I can do this with no issues as it is a polygon but with the circle I would have to ignore it if I placed these controls.
Also a tad harder to set a single node to the edge and have the shape touch the edge still needs a bit of help of another node to squish it to touch.
However I am a bit neutral.
Or I should make a behaviour change from one to another on given conditions?



