I'm coding a 2D painting application named Animatoon.
In Animatoon, I have the possibility to draw stroke/line with Brush-image (drawalphaimage()) or circle().
But, I'm looking for a way to have a good result for the stroke : curve smoothed and AA line with Brushsize/circleSize < 5.
For the moment, my strokes are good enough for "dirty or rough" presets (crayon, Charcoals, chalk, acrylics, watercolor) and for big inking, but not good for precise inking (1 to 6 pixels), because the result isn't really smoothed and antialiased.
I have found for now 4 ways (technics) to draw the lines/strokes :
- LSI technic (found here : http://www.purebasic.fr/english/viewtop ... =Bresenham) - Good for 5 to 12 pixels for AA line. > 13 : line are full (effect is interesting but not for inking line)
- HB technic (Draw dash : http://www.development-lounge.de/viewto ... =16&t=6291) : good for > 3 pixels, not bad for inking (with a smoothed brush)
- G-rom+Falsam technic (http://www.purebasic.fr/french/viewtopi ... 87#p172887) : good for > 5 pixels
- My personnal technic (rough) : not bad if > 5px, but the curve need to be smoothed.
2 pictures to show the result and what I would like to have as result :
Animatoon vs Photoshop :
http://www.dracaena-studio.com/animatoo ... stroke.jpg
Animatoon (LSI and G-rom/Falsam Technic, for stroke)
http://www.dracaena-studio.com/animatoo ... troke2.jpg
1) Create Curve
I have found this code, but I don't know how I can convert it to purebasic :
Pencil2D (https://github.com/pencil2d/pencil/blob ... shtool.cpp) :
Code: Select all
qreal distance = 4 * QLineF( b, a ).length();
int steps = qRound( distance ) / brushStep;
for ( int i = 0; i < steps; i++ )
{
QPointF point = lastBrushPoint + ( i + 1 ) * ( brushStep )* ( b - lastBrushPoint ) / distance;
rect.extend( point.toPoint() );
mScribbleArea->drawBrush( point,
brushWidth,
offset,
mEditor->color()->frontColor(),
opacity );
if ( i == ( steps - 1 ) )
{
lastBrushPoint = point;
}
}
2) Antialiased Line (for line < 10 pixels)
Antialiased Thickline (bresenham), I don't know how to convert it in purebasic :
Code: Select all
void plotLineWidth((int x0, int y0, int x1, int y1, float wd)
{
int dx = abs(x1-x0), sx = x0 < x1 ? 1 : -1;
int dy = abs(y1-y0), sy = y0 < y1 ? 1 : -1;
int err = dx-dy, e2, x2, y2; /* error value e_xy */
float ed = dx+dy == 0 ? 1 : sqrt((float)dx*dx+(float)dy*dy);
for (wd = (wd+1)/2; ; ) { /* pixel loop */
setPixelColor(x0,y0,max(0,255*(abs(err-dx+dy)/ed-wd+1));
e2 = err; x2 = x0;
if (2*e2 >= -dx) { /* x step */
for (e2 += dy, y2 = y0; e2 < ed*wd && (y1 != y2 || dx > dy); e2 += dx)
setPixelColor(x0, y2 += sy, max(0,255*(abs(e2)/ed-wd+1));
if (x0 == x1) break;
e2 = err; err -= dy; x0 += sx;
}
if (2*e2 <= dy) { /* y step */
for (e2 = dx-e2; e2 < ed*wd && (x1 != x2 || dx < dy); e2 += dy)
setPixelColor(x2 += sx, y0, max(0,255*(abs(e2)/ed-wd+1));
if (y0 == y1) break;
err += dx; y0 += sy;
}
}
}
3) Smooth Curves
Here, I have adapted a code from a friend, to smooth my curve, I think it's a good start to have smoothed curve/stroke.
Do you think it's a good for smoothed curve or is there a better way (faster, better result) ?
Code: Select all
; Code by Onilink (CBNA) 2011
; adapted for Purebasic 5.31 by blendman 06/2015 (for Animatoon)
; distance, direction..
Macro point_distance(x1,y1,x2,y2)
Int(Sqr((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) )
EndMacro
Macro point_direction(x1,y1,x2,y2)
ATan2((y2- y1),(x2- x1))
EndMacro
Global Dim listdot.point(0), startCurve, size
size = 5
Procedure AddDot(x1,y1,size = 1)
i = ArraySize(listdot())
If i >0
xx = x1
yy = y1
StartX = ListDot(i-1)\x
StartY = ListDot(i-1)\y
dist = point_distance(xx,yy,StartX,StartY)
If dist > size
ok = 1
EndIf
Else
ok = 1
EndIf
If ok
ListDot(i)\x = x1
ListDot(i)\y = y1
ReDim ListDot(i+1)
EndIf
EndProcedure
Procedure AddArrayElement(Array liste1.point(1),x1,y1)
i = ArraySize(liste1())
liste1(i)\x = x1
liste1(i)\y = y1
ReDim liste1(i+1)
EndProcedure
Procedure CurveStroke()
; we get the current dot
Dim copy.point(0)
n = ArraySize(listdot()) - 3
; we copy it to a new array, to smooth it
For i=0 To 3
ReDim copy(i)
copy(i)\x = listdot(n+i)\x
copy(i)\y = listdot(n+i)\y
Next i
; smooth the dots
Dim tmp.point(0)
bx1 = (copy(0)\x + copy(1)\x)/2
by1 = (copy(0)\y + copy(1)\y)/2
bx2 = (copy(1)\x + copy(2)\x)/2
by2 = (copy(1)\y + copy(2)\y)/2
AddArrayElement(tmp(), bx1,by1)
AddArrayElement(tmp(), (bx1+copy(1)\x)/2,(by1+copy(1)\y)/2)
AddArrayElement(tmp(), (bx2+copy(1)\x)/2,(by2+copy(1)\y)/2)
AddArrayElement(tmp(), bx2, by2)
ReDim copy(0)
For i= 0 To 4
AddArrayElement(copy(),tmp(i)\x,tmp(i)\y)
Next i
ReDim tmp(0)
bx1 = (copy(0)\x + copy(1)\x)/2
by1 = (copy(0)\y + copy(1)\y)/2
bx2 = (copy(2)\x + copy(3)\x)/2
by2 = (copy(2)\y + copy(3)\y)/2
bx = (copy(1)\x + copy(2)\x)/2
by = (copy(1)\y + copy(2)\y)/2
AddArrayElement(tmp(), copy(0)\x,copy(0)\y)
AddArrayElement(tmp(), bx1,by1)
AddArrayElement(tmp(), (bx+copy(1)\x)/2, (by+copy(1)\y)/2 )
AddArrayElement(tmp(), (bx+copy(2)\x)/2, (by+copy(2)\y)/2 )
AddArrayElement(tmp(), bx2,by2)
AddArrayElement(tmp(), copy(3)\x,copy(3)\y)
If startCurve
tmp(0)\x = ListDot(0)\x
tmp(0)\y = ListDot(0)\y
startCurve = 0
EndIf
; and we can draw the result
If StartDrawing(CanvasOutput(0))
For i=0 To ArraySize(tmp())-2
LineXY(tmp(i)\x,tmp(i)\y,tmp(i+1)\x,tmp(i+1)\y,RGBA(255,0,0,255))
Next i
;; original line, Not smoothed
For i=0 To ArraySize(listdot())-2
LineXY(listdot(i)\x,listdot(i)\y,listdot(i+1)\x,listdot(i+1)\y,RGBA(0,0,0,255))
Next i
StopDrawing()
EndIf
FreeArray(copy())
FreeArray(tmp())
EndProcedure
OpenWindow(0, 0, 0, 1024,768, "Smooth curve", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
CanvasGadget(0,0,0,1024,768)
Repeat
Event = WaitWindowEvent(1)
EventType = EventType()
EventGadget = EventGadget()
Select Event
Case #PB_Event_CloseWindow
Break
Case #PB_Event_Gadget
Select EventGadget
Case 0
If EventType = #PB_EventType_LeftButtonDown
MouseDown = #True
startCurve = 1
ElseIf EventType = #PB_EventType_LeftButtonUp
MouseDown = #False
startCurve = 0
ReDim listdot(0)
ElseIf EventType = #PB_EventType_MouseMove
If MouseDown
MouseX = GetGadgetAttribute(0,#PB_Canvas_MouseX)
MouseY = GetGadgetAttribute(0,#PB_Canvas_MouseY)
AddDot(MouseX,MouseY,4)
If ArraySize(listdot()) > 2
CurveStroke()
EndIf
EndIf
EndIf
EndSelect
EndSelect
Until Event = #PB_Event_CloseWindow
And finally, another test to draw the strokes.
The result is not bad if size >= 5, but not good if size < 4 (it's too much pixelised)).
Code: Select all
; by falsam and G-rom
w = 1024
h = 768
OpenWindow(0,0,0,w,h,"PAINT", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
CanvasGadget(0,105,0,910,768,#PB_Canvas_ClipMouse)
TrackBarGadget(1,0,50,100,20,1,50)
GadgetToolTip(1,"Size")
TrackBarGadget(2,0,80,100,20,0,255)
GadgetToolTip(2,"Alpha")
SpinGadget(3,0,110,100,20,1,200,#PB_Spin_Numeric)
GadgetToolTip(3,"Interval")
ButtonGadget(4,20,140,20,20,"Clear")
mx.i
my.i
paint.b = #False
Structure Vector2f
x.f : y.f
EndStructure
Procedure.f Distance(*a.Vector2f, *b.Vector2f)
ProcedureReturn Sqr( ((*a\x-*b\x)*(*a\x-*b\x)) + ((*a\y-*b\y)*(*a\y-*b\y)))
EndProcedure
PaintPos.Vector2f
OldPaintPos.Vector2f
R = 10 ; rayon
Alpha = 10
interval.f = 3
SetGadgetState(3,interval)
SetGadgetState(1,R)
SetGadgetState(2,Alpha)
Pas.f = GetGadgetState(3)*0.01
While #True
Repeat
event = WindowEvent()
If event = #PB_Event_CloseWindow
Break 2
EndIf
If event = #PB_Event_Gadget
If EventGadget() = 0
Select EventType()
Case #PB_EventType_MouseMove
mx = GetGadgetAttribute(0,#PB_Canvas_MouseX)
my = GetGadgetAttribute(0,#PB_Canvas_MouseY)
PaintPos\x = mx
PaintPos\y = my
Case #PB_EventType_LeftButtonDown
paint = #True
mx = GetGadgetAttribute(0,#PB_Canvas_MouseX)
my = GetGadgetAttribute(0,#PB_Canvas_MouseY)
OldPaintPos\x = mx
OldPaintPos\y = my
Case #PB_EventType_LeftButtonUp
paint = #False
EndSelect
ElseIf EventGadget() = 1 ; size
R= GetGadgetState(1)
ElseIf EventGadget() = 2 ; alpha
Alpha = GetGadgetState(2)
ElseIf EventGadget() = 4 ; clear
If StartDrawing(CanvasOutput(0))
Box(0,0,2000,2000,RGB(255,255,255))
StopDrawing()
EndIf
ElseIf EventGadget() = 3 ; interval
Pas = GetGadgetState(3)*0.01
EndIf
EndIf
Until event = 0
If paint
If pas * R > 0
interval.f = Pas * R
Else
interval = 1
EndIf
dist.f = Distance(@PaintPos, @OldPaintPos)
If(dist > interval)
number.i = dist / interval
;If number = 1 ; slow speed
; If dist >
; If StartDrawing(CanvasOutput(0))
; DrawingMode(#PB_2DDrawing_AlphaBlend)
; Circle(PaintPos\x,PaintPos\y,R,RGBA(0,0,0,100))
; StopDrawing()
; EndIf
; EndIf
;Else
Direction.Vector2f
Direction\x = OldPaintPos\x - PaintPos\x
Direction\y = OldPaintPos\y - PaintPos\y
lenght.f = Sqr(Direction\x * Direction\x + Direction\y * Direction\y)
If lenght > 0
Direction\x / lenght
Direction\y / lenght
EndIf
If StartDrawing(CanvasOutput(0))
DrawingMode(#PB_2DDrawing_AlphaBlend)
For i = 0 To number-1
px.d = PaintPos\x + (Direction\x*(interval*i))
py.d = PaintPos\y + (Direction\y*(interval*i))
Circle(px,py,R,RGBA(0,0,0,Alpha))
Next
StopDrawing()
EndIf
;EndIf
OldPaintPos\x = PaintPos\x
OldPaintPos\y = PaintPos\y
EndIf
EndIf
Wend
If you have suggestions, code or information to have a better result for smoothed and AA stroke/Line, I'm very interested

Thank you a lot.