Page 1 of 2

Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 9:59 am
by Joubarbe
Hi,

This question is not only related to PureBasic but to coding in general. I'm wondering how to interact with a complex 2D drawing, like a country. For example, I would like to be able to highlight the border of this country when the user selects it. I would also like to be able to apply filters on a world map, to show population on a coloured-scale, from red to green.

Currently, I've got a little program that manage to detect what country the user has selected (by simple left-clicking). To do that, I've made two maps ; one to display and another one, on which I've coloured every countries with a different color (using Photoshop). When the user clicks on the map, I look at the coordinates on the "technical map", and retrieve the country via its $colour. I don't use a screen.

But with this technique, I'm unable to achieve the two points mentionned above. Analyzing each pixel and redraw on top of the displayed map takes too much time (it's a very big map).

So what should I do ? There must be a theoritical method to do this ? For example, would it be better to use sprites ? I don't clearly see the need of using them when I should.

I don't know if I'm clear enough here, but thanks anyway for reading :)

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 10:11 am
by IdeasVacuum
Well, instead of one big image, have a set or group of images (tiles). Then you have a grid of images to work with, and your code can first detect which image gadget is active, and then the pixel position of the cursor within gadget - you can then use your technique of swapping images.

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 10:21 am
by Fig
A solution would be to do a sprite of each country... But it'll be a lot of work.
If you do so, you will be able to detect a clic on them by using SpritePixelCollision().
Then you can display a larger sprite of your country, under the current one, to select the fringe of it. (by using zoomsprite)

An other way to attempt it, would be to vectorise your countries, but it's more complex (i am thinking: you clic on the black and white country, you filled it with an algo like djikstra-filled4 etc-, then you detect the pixels side...) I would personnaly go with that solution as i'am lazy about doing sprites of all countries... :mrgreen: (but it's technicaly more complex)
Doing this you can also detect wich country has been selected by analysing the pixel's size of the filled country for exemple.

Can you post your map ?

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 11:12 am
by Joubarbe
@IdeasVacuum : I'm working with one canvas in which I draw the map. Thus, I'm able to scroll and zoom. Even if I could split the map into several images, I don't see how your technique will help to draw borders and fill an entire country with a color.

@Fig : If I can avoid sprites, I will. I'll search for this vectorization technique, whose I know nothing :)
But the sprites technique is perhaps better. I mean, I already have layers of every countries on my .psd map file. However, is there a fast way to fill a sprite with a color (for the "filters" part of my first question) ? And by fast, I mean that the user needs to see 190 countries with a new color instantly after clicking on a filter.
And with your technique, how would you "build" the world map ? By manually place every sprites ?

Post my map ? You mean the image ? It's a simple planisphere.

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 11:13 am
by Fig
Yes, your map, cause we may give you an exemple how to achieve it if you want...

With sprite you can use DisplayTransparentSprite(#Sprite, x, y [, Intensity [, Color]]) to give it the desired color


Well, i would click on every country, make the algo fill them and enter their name. The prog save all these information in a file i would use for my main program (ie number of pixel per country, names and x,y to fill this specific country)
The fill4 algo should be fast enough to fill every country you need.

But if you already have a layer for each country it should be really easy to do with sprites. (openwindowsscreen())

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 11:35 am
by Joubarbe
Yes, it seems easier with sprites. I make one image per country, then place them according to their position in my full map image.

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 12:21 pm
by IdeasVacuum
I make one image per country, then place them according to their position in my full map image.
That is a similar approach to the one I described, without using sprites. You can place ImageGadgets on a ScrollAreaGadget, and the picked image can be swapped with another where a country is highlighted by colour change and/or bold edge. Instead of ImageGadgets, you could tile CanvasGadgets, which would lend some handy mouse functionality.

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 2:13 pm
by Joubarbe
Thanks for the reference to the "fill4" algo (which I suppose is the Flood Fill Algorithm).

After reading that, I wrote the following code :

Code: Select all

Procedure Vectorize(PixelX, PixelY, PixelColor$)
  Shared Pixels()
  
  If PixelColor$ <> "25314a" ; color of the sea
    AddElement(Pixels())
    Pixels()\x = PixelX
    Pixels()\y = PixelY
    PixelColorN$ = GetPixelColor(PixelX, PixelY - 1, #Img_TechnicalMap)
    PixelColorS$ = GetPixelColor(PixelX, PixelY + 1, #Img_TechnicalMap)
    PixelColorW$ = GetPixelColor(PixelX - 1, PixelY, #Img_TechnicalMap)
    PixelColorE$ = GetPixelColor(PixelX + 1, PixelY, #Img_TechnicalMap)
    StartDrawing(ImageOutput(#Img_TechnicalMap))
    Plot(PixelX, PixelY, $FF00FF)
    StopDrawing()
    SetGadgetState(#Gdt_Image, ImageID(#Img_TechnicalMap))
    If PixelColorN$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX
      Pixels()\y = PixelY - 1
      Vectorize(PixelX, PixelY - 1, PixelColor$)
    EndIf
    If PixelColorS$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX
      Pixels()\y = PixelY + 1
      Vectorize(PixelX, PixelY + 1, PixelColor$)
    EndIf
    If PixelColorW$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX - 1
      Pixels()\y = PixelY
      Vectorize(PixelX - 1, PixelY, PixelColor$)
    EndIf
    If PixelColorE$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX + 1
      Pixels()\y = PixelY
      Vectorize(PixelX + 1, PixelY, PixelColor$)
    EndIf
  EndIf
EndProcedure
It works for a time, then crashes. I'm guessing there are some stack problems ? I've never encountered such problems, so I have no idea how to avoid them :)

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:31 pm
by Fig
Sorry, i didn't know the english name of the algo. (fill in 4 directions)

Yes... Well, Pb had also that kind of ... FillArea(x, y, BorderColor [, Color]) :oops:

And it's not really a vectorisation lol. (in my post i jumped from an idea to an other, i wasn't clear)
The vectorisation would be to record coordonates of country's shapes in order to be able to change the size or highlight the fringe...

Anyway it's a good job. Does it fits your need ?
(You should plot all the points at the end, out of the procedure, because it's not good for performance to use lots of start/stopdrawing() in a recursive procedure, your crashs comes probably from that.)

Do you abandon the sprite idea ? It was the easiest one...

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:39 pm
by Joubarbe
Damn, I didn't see the FillArea() function... I always have the help open, but never saw this. Thanks!

Anyway, it's still crashing, without the StartDrawing() part.

EDIT : FillArea() is not the perfect solution for me. Even though it can be used to apply filters, it doesn't help to highlight borders only (to see what country the user has selected).
EDIT 2 : No, the sprites technique is probably the best one ; but I prefer to have choices, and have a working program with the Flood Fill Algorithm.

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:44 pm
by Fig
Anyway, if you want to walk that path, you will need to keep track of the most external pixels to know the shape of the country...

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:47 pm
by Joubarbe
I know, I will save pixels into an external file. That's not the hardest part :)
Currently, this is the crash!

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:52 pm
by Fig
Ok, let me see...

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 4:56 pm
by Joubarbe
This is the full code :

Code: Select all

Enumeration
  #Img_TechnicalMap
  #Gdt_ScrollArea
  #Gdt_Image
EndEnumeration

;UseSQLiteDatabase()
UsePNGImageDecoder()

LoadImage(#Img_TechnicalMap, "Influence_TechnicalMap.png")

NewList Pixels.Point()

Procedure$ GetPixelColor(PixelX, PixelY, Image)
  StartDrawing(ImageOutput(Image))
  PixelColor$ = RSet(Hex(Point(PixelX, PixelY)), 6, "0")
  PixelColor$ = Right(PixelColor$, 2) + Mid(PixelColor$, 3, 2) + Left(PixelColor$, 2)
  PixelColor$ = LCase(PixelColor$)
  StopDrawing()
  ProcedureReturn PixelColor$
EndProcedure

Procedure Vectorize(PixelX, PixelY, PixelColor$)
  Shared Pixels()
  
  If PixelColor$ <> "25314a"
    AddElement(Pixels())
    Pixels()\x = PixelX
    Pixels()\y = PixelY
    PixelColorN$ = GetPixelColor(PixelX, PixelY - 1, #Img_TechnicalMap)
    PixelColorS$ = GetPixelColor(PixelX, PixelY + 1, #Img_TechnicalMap)
    PixelColorW$ = GetPixelColor(PixelX - 1, PixelY, #Img_TechnicalMap)
    PixelColorE$ = GetPixelColor(PixelX + 1, PixelY, #Img_TechnicalMap)
    SetGadgetState(#Gdt_Image, ImageID(#Img_TechnicalMap))
    If PixelColorN$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX
      Pixels()\y = PixelY - 1
      Vectorize(PixelX, PixelY - 1, PixelColor$)
    EndIf
    If PixelColorS$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX
      Pixels()\y = PixelY + 1
      Vectorize(PixelX, PixelY + 1, PixelColor$)
    EndIf
    If PixelColorW$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX - 1
      Pixels()\y = PixelY
      Vectorize(PixelX - 1, PixelY, PixelColor$)
    EndIf
    If PixelColorE$ = PixelColor$
      AddElement(Pixels())
      Pixels()\x = PixelX + 1
      Pixels()\y = PixelY
      Vectorize(PixelX + 1, PixelY, PixelColor$)
    EndIf
;     StartDrawing(ImageOutput(#Img_TechnicalMap))
;     Plot(PixelX, PixelY, $FF00FF)
;     StopDrawing()
  EndIf
EndProcedure

OpenWindow(0, 0, 0, 1920, 1080, "")
ScrollAreaGadget(#Gdt_ScrollArea, 0, 0, 1920, 1060, ImageWidth(#Img_TechnicalMap), ImageHeight(#Img_TechnicalMap))
ImageGadget(#Gdt_Image, 0, 0, 0, 0, ImageID(#Img_TechnicalMap))

Repeat 
  Event = WaitWindowEvent()
  
  If Event = #PB_Event_Gadget And EventType() = #PB_EventType_LeftClick And EventGadget() = #Gdt_Image
    PixelX = WindowMouseX(0) + GetGadgetAttribute(#Gdt_ScrollArea, #PB_ScrollArea_X)
    PixelY = WindowMouseY(0) + GetGadgetAttribute(#Gdt_ScrollArea, #PB_ScrollArea_Y)
    PixelColor$ = GetPixelColor(PixelX, PixelY, #Img_TechnicalMap)
    Vectorize(PixelX, PixelY, PixelColor$)
  EndIf
  
Until Event = #PB_Event_CloseWindow

Re: Draw a 2D non geometric figure

Posted: Wed Apr 09, 2014 5:19 pm
by eesau
You should keep a record of pixels already checked, otherwise you will surely run out of stack with your function. Just make a check at the beginning of your function to see whether the pixel is already checked, and if so, return immediately.

I also recommend to ditch the strings, the way it's now is probably x1000 slower than it could be.