Skip to content

Week 7 recap - vector approach

Monday, 15 July 2024 | Ken Lo


Currently, I have three separate ideas. First idea is that in kis_tool_freehand where stroke initialization and end stroke exists, we populate a vector, filter out extra points and then take out corner pixels before passing it down. The problem I am running into with this method is that I can't exactly figure out whats the best way to access this vector because trying to access it as a global vector seems risky and also doesn't work because putting #include "plugins/paintops/defaultpaintops/brush/kis_brushop.h" in kis_tool_freehand seems to have directory issues. Maybe I should try storing the vector another way? This time I will try passing it down, but I am a little worried if this will work because, in kis_tool_freehand, I have smoothedPoints ( the filtered points vector) declared in endStroke() which isn't part of the function hierarchy. I will still try to pass a duplicate vector through the paintEvent in doStroke all the way to paintLine in brushop to see if it works.

For reference this is the function hierarchy:
Beginning ->void KisToolProxy::forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint)

->void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event){ initStroke(event);} ... which is adjacent to ->

KisToolFreeHand::doStroke(KoPointerEvent *event) takes in a (KoPointerEvent *event - assuming its the click and drag event )

->KisToolFreehandHelper::paintEvent(KoPointerEvent *event)

  • initializes the KisPaintInformation (alot of info + elapsedStrokeTime) and KisUpdateTimeMonitor which reports mouse movement and then calls paint(info)

->KisToolFreehandHelper::paint(KisPaintInformation &info)

  • smooths coordinates out using history and the distance. Important to note that pixel tool is using simple smoothing here and that it calls paintBezierSegment inside here. I think right here is when it calculates/gets KisPaintInformation pi1, KisPaintInformation pi2 which is the start and end coordinates of the stroke.

->KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2)

  • computes and paints a Bezier curve segment between two points. Then it calls paintBezierCurve with the start point, control points, and end points to actually render the bezier curve. (paintBezierCurve(pi1,control1,control2,pi2);) calling without putting the parameter KisDistanceInformation *currentDistance, will call paintBezierCurve again with 0 for the distance.

->KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance)

  • Calling this function will call paintBezierCurve again but with a new parameter this which is the missing KisPaintOp *paintOp (deals with brush settings)

->KisPaintOp::paintBezierCurve with 1 paintop*

  • This function recursively subdivides and paints a cubic Bezier curve segment. It either draws the curve directly if it is sufficiently flat or continues to subdivide the curve until flatness is achieved. When it is sufficient it calls on paintLine

KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance)

  • handles the painting of a line segment between two points (pi1 and pi2). Depending on specific conditions related to brush sharpness and size, it either performs a custom painting operation or delegates the task to a base class function. If sharpness is on and brush is defined with width and height of 1, we initialize / clear line cache device. Then a KisPainter object p is created with m_lineCacheDevice as its target and painter color gets set and drawDDALine ( Digital Differential Analyzer (DDA) )method gets called on that painter object to draw a line from pi1.pos() to pi2.pos(). Afterwards, the rc is obtained from m_lineCacheDevice->extent() and bitblt gets called on the painter to copy the line from m_lineCacheDevice to the painters target canvas. If the sharpness option is not enabled or the brush size does not meet the specified criteria, the function calls KisPaintOp::paintLine(pi1, pi2, currentDistance), which is the base class method for painting a line.

KisPainter::drawDDALine(const QPointF & start, const QPointF & end)

  • Plots point based on start and end, but is called so often that it is basically just plotting each individual point when drawing slow strokes.

Second idea is a 3 pixel approach where we track current/lastdrawn/waiting pixel see blog 1 for more info, basically if currentpixel deviates from the x/y axis of lastdrawn pixel we skip over the intermediary waiting pixel. I've been trying this out in kis_brushop where paintLine gets called but the problem with this is that it's difficult to set the three points lastDrawnPixel, currentPixel and waitingPixel when all we have is a bunch of start and end points fed in. I think it is probably possible with some sort of queue system where the points get read slower but there are two problems here, 1.) we might run into a situation where nothing gets drawn until the stroke is let go, and 2.) here in kis_brushop we don't have access to when a stroke ends its only responsible for painting given a start and end point.

The third attempt is to use this somehow to modify the stabilizers to send out proper points so that drawDDALine can automatically make pixel-perfect lines. Still, the problem with this is that if we have a horizontal line straight into a vertical line (and vice versa) the corner where they meet will be non-pixel perfect.