Drawing beautifull line (AA, brush image...)

Just starting out? Need help? Post your questions and find answers here.
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Drawing beautifull line (AA, brush image...)

Post by [blendman] »

Hi
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.
Last edited by [blendman] on Mon Jun 29, 2015 6:28 pm, edited 1 time in total.
Julian
Enthusiast
Enthusiast
Posts: 276
Joined: Tue May 24, 2011 1:36 pm

Re: Drawing beautifull line (AA, brush image...)

Post by Julian »

Some nice routines there.

There's a bug in number 3 in Procedure AddDot

Code: Select all

    StartX = ListDot(i)\x
    StartY = ListDot(i)\y
should be

Code: Select all

    StartX = ListDot(i-1)\x
    StartY = ListDot(i-1)\y
As you're comparing the coordinates of the last entry in the array which hasn't been set until the very end of the procedure, so the start point is always 0,0
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Re: Drawing beautifull line (AA, brush image...)

Post by [blendman] »

Julian wrote:There's a bug in number 3 in Procedure AddDot
Thanks, I have modified the first post (and add a parameter "size" in the procedure AddDot(x,y,size)).
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Re: Drawing beautifull line (AA, brush image...)

Post by blueznl »

'Oversampling' is an option, perhaps.

1. Create a temp bitmap 4, 9 or 16 times the actual target result.
2. Draw with the effect you prefer on that high res bitmap.
3. When the drawing is done reduce the size of the temp bitmap.
4. Apply the resulting temp bitmap to the original.

It's a bit slower than direct image manipulation, but it just might work. I think there are some packages that actually do this, for example the old PhotoImpact, and looking at their behaviour perhaps Kryta and PaintTool Sai too.

(Though frankly I'm not the artist, my daughter is :-))
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Post Reply