Converting image to Vector Drawing commands

Everything else that doesn't fall into one of the other PB categories.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Converting image to Vector Drawing commands

Post by netmaestro »

I was quite impressed with the work davido did in creating a scalable set of chess pieces. I'm familiar with the design he used but unfortunately for me it isn't my favorite. I wanted to create a similar set with the pieces I'm used to using so I tried to find a simple, as automated as possible, way to do it. I pm'd davido and asked how he did it and I confess to being disappointed with his answer. He said he did it all by hand. I tried with no luck to find alternatives and eventually gave in to the reality that I'm going to have to do the same. I started with the white king, copied it out from my sprite set and set to work. Here is the method I used:

-Load the image into Photoshop and mark all corners with red dots.
-Use the rectangular selection tool to find the precise locations of the dots and record them.
-Load the image into PB, remove the fill color leaving just the lines and display in a window at a reduced alpha.
-Use AddPathCurve and AddPathLine to go around the outside of the image, one curve or line at a time. This took about two hours of trial and error.
-I wrote all my path commands with hardcoded numbers. The image is 300x300 so most of the numbers were inside those dimensions.
-Once I was able to trace a path that would fill correctly, I added fill commands.
-Then I added the rest of the interior lines.

At this point I had a set of commands that would draw a nice white king at 300x300 pixels in size. But it needs to be scalable. To this end I wrote a helper program that I could plug my hardcoded numbers into and it would give a scalable equivalent.

Plug:

Code: Select all

AddPathCurve(92,26,-42,140,77,199)
into it and it would reply with

Code: Select all

AddpathCurve(0.3067*sz,0.0867*sz,-0.1400*sz,0.4667*sz,0.2567*sz,0.6633*sz)
Once the helper was written, converting the commands didn't take much time at all. Anyway, if you're interested in this kind of thing, here's what I did:

Design program:

Code: Select all

Procedure Transparency(x,y, sourcecolor, targetcolor)
  If sourcecolor=RGBA(255,255,255,255)
    ProcedureReturn targetcolor
  Else
    ProcedureReturn sourcecolor
  EndIf
EndProcedure  

imagefile$ = GetTemporaryDirectory()+"netmaestro-wk.png"
If FileSize(imagefile$) = -1
  InitNetwork()
  If Not ReceiveHTTPFile("https://lloydsplace.com/wk.png", imagefile$)
    MessageRequester("oops", "Could not download the image")
    End
  EndIf
EndIf

Global sz.d=700

UsePNGImageDecoder()
img0 = LoadImage(#PB_Any, imagefile$)

; First remove the fill color
CreateImage(0,300,300,32,#PB_Image_Transparent)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_CustomFilter)
  CustomFilterCallback(@Transparency())
  DrawAlphaImage(ImageID(img0),0,0)
StopDrawing()

; Create a new image, draw the picture on it with a low alpha to trace our lines on
CreateImage(1, sz, sz, 32, #PB_Image_Transparent)
StartVectorDrawing(ImageVectorOutput(1))
  MovePathCursor(0,0)
  DrawVectorImage(ImageID(0),50,sz,sz)
  MovePathCursor(0.5*sz,0.5*sz)
  AddPathCurve(0.6933*sz,0.0867*sz,1.1400*sz,0.4667*sz,0.7433*sz,0.6633*sz)
  AddPathLine(0.7447*sz,0.8366*sz)
  AddPathCurve(0.6333*sz,0.9100*sz,0.3666*sz,0.9100*sz,0.2533*sz,0.8366*sz) ;0.2533*sz,0.8366*sz
  AddPathLine(0.2567*sz,0.6633*sz)
  AddPathCurve(-0.1400*sz,0.4667*sz,0.3067*sz,0.0867*sz,0.5*sz,0.5*sz)
  VectorSourceLinearGradient(0.0933*sz,0.4733*sz,0.9067*sz,0.4733*sz)
  VectorSourceGradientColor(RGBA(255,255, 255, 255), 0.0)
  VectorSourceGradientColor(RGBA(230, 230, 230, 255), 0.8)
  VectorSourceGradientColor(RGBA(200,200, 200, 255), 1.0)
  FillPath(#PB_Path_Preserve)
  VectorSourceColor(RGBA(0,0,0,255))
  StrokePath(0.0100*sz) ; I use this width at design time
  ;StrokePath(0.0366*sz) ; This width is for the finished product
  MovePathCursor(0.4300*sz,0.3900*sz)
  AddPathCurve(0.4333*sz,0.3600*sz,0.3833*sz,0.2600*sz,0.5*sz,0.22*sz)
  AddPathCurve(0.6166*sz,0.2600*sz,0.5666*sz,0.3600*sz,0.5700*sz,0.3900*sz)
  AddPathLine(0.5*sz,0.5*sz)
  AddPathLine(0.4300*sz,0.3900*sz)
  VectorSourceLinearGradient(0.4200*sz,0.3200*sz,0.5800*sz,0.3200*sz)
  VectorSourceGradientColor(RGBA(255,255, 255, 255), 0.0)
  VectorSourceGradientColor(RGBA(220, 220, 220, 255), 0.8)
  VectorSourceGradientColor(RGBA(190,190, 190, 255), 1.0)
  FillPath(#PB_Path_Preserve)
  MovePathCursor(0.5*sz,0.49*sz)
  AddPathLine(0.5*sz,0.6033*sz)
  MovePathCursor(0.5*sz,0.073*sz)
  AddPathLine(0.5*sz,0.22*sz)
  MovePathCursor(0.44*sz,0.121*sz)
  AddPathLine(0.56*sz,0.121*sz)
  VectorSourceColor(RGBA(0,0,0,255))
  StrokePath(0.0100*sz) ; I use this width at design time
  ;StrokePath(0.0366*sz,#PB_Path_RoundEnd) ; This width is for the finished product
  MovePathCursor(0.2533*sz,0.6633*sz)
  AddPathCurve(0.3666*sz,0.5900*sz,0.6333*sz,0.5900*sz,0.7433*sz,0.6633*sz)
  MovePathCursor(0.2533*sz,0.7466*sz)
  AddPathCurve(0.3666*sz,0.6766*sz,0.6333*sz,0.6766*sz,0.7433*sz,0.7433*sz)
  MovePathCursor(0.2533*sz,0.8366*sz)
  AddPathCurve(0.3666*sz,0.7633*sz,0.6333*sz,0.7633*sz,0.7433*sz,0.8366*sz)
  StrokePath(0.0100*sz) ; I use this width at design time
  ;StrokePath(0.0366*sz) ; This width is for the finished product
StopVectorDrawing()

OpenWindow(0,0,0,sz,sz,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
StickyWindow(0,1)
ImageGadget(0,0,0,0,0,ImageID(1))
Repeat:Until WaitWindowEvent()=#PB_Event_CloseWindow
Helper:

Code: Select all

Global sz.d=300

Procedure.s ReplaceCurve(a.d,b.d,c.d,d.d,e.d,f.d)
  a.d = a/sz
  b.d = b/sz
  c.d = c/sz
  d.d = d/sz
  e.d = e/sz
  f.d = f/sz
  
  a$ = StrD(a,4) + "*sz,"
  b$ = StrD(b,4) + "*sz,"
  c$ = StrD(c,4) + "*sz,"
  d$ = StrD(d,4) + "*sz,"
  e$ = StrD(e,4) + "*sz,"
  f$ = StrD(f,4) + "*sz"
  
  ProcedureReturn "AddpathCurve("+a$+b$+c$+d$+e$+f$+")"
  
EndProcedure

Debug ReplaceCurve(92,26,-42,140,77,199)
And the final product:

Code: Select all

Procedure DrawWhiteKing(sz.d, linecolor.q, fillcolor.q = 0, gradient.b = 0)
  result = CreateImage(#PB_Any, sz, sz, 32, #PB_Image_Transparent)
  StartVectorDrawing(ImageVectorOutput(result))
    MovePathCursor(0.5*sz,0.5*sz)
    AddPathCurve(0.6933*sz,0.0867*sz,1.1400*sz,0.4667*sz,0.7433*sz,0.6633*sz)
    AddPathLine(0.7447*sz,0.8366*sz)
    AddPathCurve(0.6333*sz,0.9100*sz,0.3666*sz,0.9100*sz,0.2533*sz,0.8366*sz)
    AddPathLine(0.2567*sz,0.6633*sz)
    AddPathCurve(-0.1400*sz,0.4667*sz,0.3067*sz,0.0867*sz,0.5*sz,0.5*sz)
    FillPath(#PB_Path_Preserve)
    VectorSourceColor(linecolor)
    StrokePath(0.0366*sz,#PB_Path_Preserve)
    MovePathCursor(0.4300*sz,0.3900*sz)
    AddPathCurve(0.4333*sz,0.3600*sz,0.3833*sz,0.2600*sz,0.5*sz,0.22*sz)
    AddPathCurve(0.6166*sz,0.2600*sz,0.5666*sz,0.3600*sz,0.5700*sz,0.3900*sz)
    AddPathLine(0.5*sz,0.5*sz)
    AddPathLine(0.4300*sz,0.3900*sz)
    If gradient
      VectorSourceLinearGradient(0.0000*sz,0.5000*sz,1.0000*sz,0.5000*sz)
      VectorSourceGradientColor(RGBA(255,255, 255, 255), 0.0)
      VectorSourceGradientColor(RGBA(220, 220, 220, 255), 0.8)
      VectorSourceGradientColor(RGBA(190,190, 190, 255), 1.0)
    Else
      VectorSourceColor(fillcolor)
    EndIf
    FillPath(#PB_Path_Preserve)
    MovePathCursor(0.5*sz,0.5*sz)
    AddPathLine(0.5*sz,0.6033*sz)
    MovePathCursor(0.5*sz,0.073*sz)
    AddPathLine(0.5*sz,0.22*sz)
    MovePathCursor(0.44*sz,0.121*sz)
    AddPathLine(0.56*sz,0.121*sz)
    VectorSourceColor(linecolor)
    StrokePath(0.0366*sz,#PB_Path_RoundEnd)
    MovePathCursor(0.2533*sz,0.6633*sz)
    AddPathCurve(0.3666*sz,0.5900*sz,0.6333*sz,0.5900*sz,0.7433*sz,0.6633*sz)
    MovePathCursor(0.2533*sz,0.7466*sz)
    AddPathCurve(0.3666*sz,0.6766*sz,0.6333*sz,0.6766*sz,0.7433*sz,0.7433*sz)
    MovePathCursor(0.2533*sz,0.8366*sz)
    AddPathCurve(0.3666*sz,0.7633*sz,0.6333*sz,0.7633*sz,0.7433*sz,0.8366*sz)
    StrokePath(0.0366*sz)
  StopVectorDrawing()
  ProcedureReturn result
EndProcedure

;test

sz=1000
result = DrawWhiteKing(sz,RGBA(0,0,0,255),RGBA(255,255,255,255),1)

; UsePNGImageEncoder()
; SaveImage(result,"wk.png",#PB_ImagePlugin_PNG)

OpenWindow(0,0,0,sz,sz,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)

StickyWindow(0,1)
ImageGadget(0,0,0,0,0,ImageID(result))
Repeat:Until WaitWindowEvent()=#PB_Event_CloseWindow
Only 11 more pieces to go. What the heck, I've got nothing better to do these days. Can't go outside or run my business, might as well fill the time with something.
Last edited by netmaestro on Mon Apr 13, 2020 8:50 pm, edited 5 times in total.
BERESHEIT
User avatar
NicTheQuick
Addict
Addict
Posts: 1227
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: Converting image to Vector Drawing commands

Post by NicTheQuick »

Why not use Inkscape and convert raster images to vector images? You then can extract all the curves from the resulting SVG file with a little bit of XML-parsing and matching to the correct PB commands.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
Post Reply