Snapping selection box

Started by wombats, June 15, 2023, 12:46:39

Previous topic - Next topic

wombats

Hi,

At some zoom levels and when it's scrolled, the selection box doesn't align properly with the pixels of the image. How can I make it better?

The tileset I'm using is from https://opengameart.org/content/tiny-16-basic.

Import MaxGUI.Drivers

Global GAME_WIDTH = 640
Global GAME_HEIGHT = 480
Global SCROLL_WIDTH = 18

Global window:TGadget = CreateWindow("My Canvas", 100, 100, GAME_WIDTH,GAME_HEIGHT, Null, WINDOW_TITLEBAR | WINDOW_CLIENTCOORDS)

Global canvas:TGadget = CreateCanvas(0, 0, GAME_WIDTH - SCROLL_WIDTH, GAME_HEIGHT - SCROLL_WIDTH, window)
Global scrollV:TGadget = CreateSlider(GAME_WIDTH - SCROLL_WIDTH, 0, SCROLL_WIDTH, GAME_HEIGHT, window, SLIDER_VERTICAL)
Global scrollH:TGadget = CreateSlider(0, GAME_HEIGHT - SCROLL_WIDTH, GAME_WIDTH, SCROLL_WIDTH, window, SLIDER_HORIZONTAL)

Global tileset:TImage = LoadImage("basictiles_2.png", 0)

SetSliderRange(scrollV, ImageHeight(tileset), GadgetHeight(canvas))
SetSliderRange(scrollH, ImageWidth(tileset), GadgetWidth(canvas))

Global zoom:Float = 1.0

Global scrollX:Int = 0
Global scrollY:Int = 0

Global leftState:Int

Global mX:Int, mY:Int

Global selectX0, selectY0, selectX1, selectY1

ActivateGadget(canvas)

CreateTimer 60

While WaitEvent()
Select EventID()
Case EVENT_TIMERTICK
RedrawGadget canvas

Case EVENT_GADGETPAINT
SetGraphics CanvasGraphics(canvas)
Local imgX:Int, imgY:Int
GetImagePosition(imgX, imgY)
SetClsColor(255, 255, 255)
Cls
SetAlpha(1.0)
SetColor(255, 255, 255)
SetScale(zoom, zoom)
DrawImage(tileset, imgX, imgY)
If leftState
SetColor(0, 0, 255)
SetAlpha(0.6)
SetScale(1.0, 1.0)
DrawRect(selectX0 * zoom, selectY0 * zoom, (selectX1 - selectX0) * zoom, (selectY1 - selectY0) * zoom)
EndIf
Flip

Case EVENT_KEYDOWN
Select EventData()
Case KEY_DOWN
DoZoomOut()
Case KEY_UP
DoZoomIn()
EndSelect

Case EVENT_MOUSEMOVE
mX = EventX()
mY = EventY()
If leftState
Local x:Int, y:Int
GetPixelPosition(mX, mY, x, y)
selectX1 = x
selectY1 = y
EndIf

Case EVENT_MOUSEDOWN
If EventData() = 1
Local x:Int, y:Int
GetPixelPosition(mX, mY, x, y)
selectX0 = x
selectY0 = y
selectX1 = x
selectY1 = y
leftState = True
EndIf

Case EVENT_MOUSEUP
If EventData() = 1
leftState = False
EndIf

Case EVENT_GADGETACTION
Select EventSource()
Case scrollH
scrollX = EventData()
Case scrollV
scrollY = EventData()
EndSelect

Case EVENT_WINDOWCLOSE
FreeGadget canvas
End

Case EVENT_APPTERMINATE
End
EndSelect
Wend

Function GetImagePosition(x:Int Var, y:Int Var)
Local imgW:Int, imgH:Int
imgW = ImageWidth(tileset) * zoom
imgH = ImageHeight(tileset) * zoom
If imgW < GadgetWidth(canvas)
x = (GadgetWidth(canvas) - imgW) / 2
Else
x = 0
EndIf
If imgH < GadgetHeight(canvas)
y = (GadgetHeight(canvas) - imgH) / 2
Else
y = 0
EndIf
x:-scrollX
y:-scrollY
EndFunction

Function DoZoomIn()
  If zoom = 0.25
    zoom:+ 0.25
  ElseIf zoom = 0.50
    zoom = 1.0
  ElseIf zoom => 1.0 And zoom <= 7.0
    zoom:+ 1.0
  EndIf
EndFunction

Function DoZoomOut()
  If zoom = 0.5
    zoom = 0.25
  ElseIf zoom = 1
    zoom = 0.50
  ElseIf zoom > 1
    zoom:- 1.0
  EndIf
EndFunction

Function GetPixelPosition(x0:Int, y0:Int, x1:Int Var, y1:Int Var)
x1 = x0 / zoom
y1 = y0 / zoom
EndFunction

Midimaster

did you already try to work with FLOATs instead of INTEGERs? Maybe the rounding in the calculation is responsible for the inaccuracy
...back from North Pole.

wombats

Yeah, it doesn't make a difference using floats from what I can tell. I think I need to start from the top-left of the image as zero and then adjust by the scrolling...but I'm not sure.

Midimaster

#3
ok I had a deeper look into the code. I downloaded the PNG and startet your code, but I do not understand what you try to do with the selection box. I see a blue rectangle that opens when clicking the mouse. But i looks to related to the tiles position in any way....

It is always there, where I click the mouse. what should I see?

I recognized, that your canvas does not move, when you move the sliders... Here is a possible solution:

You should add a hook, that reports every slider movement and reacts withs re-paint the canvas

Import MaxGUI.Drivers

Global GAME_WIDTH = 640
Global GAME_HEIGHT = 480
Global SCROLL_WIDTH = 18

Global window:TGadget = CreateWindow("My Canvas", 100, 100, GAME_WIDTH,GAME_HEIGHT, Null, WINDOW_TITLEBAR | WINDOW_CLIENTCOORDS)

Global canvas:TGadget = CreateCanvas(0, 0, GAME_WIDTH - SCROLL_WIDTH, GAME_HEIGHT - SCROLL_WIDTH, window)
Global scrollV:TGadget = CreateSlider(GAME_WIDTH - SCROLL_WIDTH, 0, SCROLL_WIDTH, GAME_HEIGHT, window, SLIDER_VERTICAL)
Global scrollH:TGadget = CreateSlider(0, GAME_HEIGHT - SCROLL_WIDTH, GAME_WIDTH, SCROLL_WIDTH, window, SLIDER_HORIZONTAL)

Global tileset:TImage = LoadImage("basictiles.png", 0)

SetSliderRange(scrollV, ImageHeight(tileset), GadgetHeight(canvas))
SetSliderRange(scrollH, ImageWidth(tileset), GadgetWidth(canvas))

Global zoom:Float = 1.0

Global scrollX:Int = 0
Global scrollY:Int = 0

Global leftState:Int

Global mX:Int, mY:Int

Global selectX0, selectY0, selectX1, selectY1

ActivateGadget(canvas)
AddHook EmitEventHook, MyHook

CreateTimer 10

While WaitEvent()
    Select EventID()
        Case EVENT_TIMERTICK
            RedrawGadget canvas

        Case EVENT_GADGETPAINT
            PaintNow()
        Case EVENT_KEYDOWN
            Select EventData()
                Case KEY_DOWN
                    DoZoomOut()
                Case KEY_UP
                    DoZoomIn()
            EndSelect

        Case EVENT_MOUSEMOVE
            mX = EventX()
            mY = EventY()
            If leftState
                Local x:Int, y:Int
                GetPixelPosition(mX, mY, x, y)
                selectX1 = x
                selectY1 = y
            EndIf
       
        Case EVENT_MOUSEDOWN
            If EventData() = 1
                Local x:Int, y:Int
                GetPixelPosition(mX, mY, x, y)
                selectX0 = x
                selectY0 = y
                selectX1 = x
                selectY1 = y
                leftState = True
            EndIf
           
        Case EVENT_MOUSEUP
            If EventData() = 1
                leftState = False
            EndIf
           
        Case EVENT_GADGETACTION
            Select EventSource()
                Case scrollH
                '    scrollX = EventData()
                Case scrollV
                '    scrollY = EventData()
            EndSelect

        Case EVENT_WINDOWCLOSE
            FreeGadget canvas
            End

        Case EVENT_APPTERMINATE
            End
    EndSelect
Wend

Function GetImagePosition(x:Int Var, y:Int Var)
    Print "get image scrollx=" + scrollx
    Local imgW:Int, imgH:Int
    imgW = ImageWidth(tileset) * zoom
    imgH = ImageHeight(tileset) * zoom
    If imgW < GadgetWidth(canvas)
        x = (GadgetWidth(canvas) - imgW) / 2
    Else
        x = 0
    EndIf
    If imgH < GadgetHeight(canvas)
        y = (GadgetHeight(canvas) - imgH) / 2
    Else
        y = 0
    EndIf
    x:-scrollX
    y:-scrollY
EndFunction

Function DoZoomIn()
  If zoom = 0.25
    zoom:+ 0.25
  ElseIf zoom = 0.50
    zoom = 1.0
  ElseIf zoom => 1.0 And zoom <= 7.0
    zoom:+ 1.0
  EndIf
EndFunction

Function DoZoomOut()
  If zoom = 0.5
    zoom = 0.25
  ElseIf zoom = 1
    zoom = 0.50
  ElseIf zoom > 1
    zoom:- 1.0
  EndIf
EndFunction

Function GetPixelPosition(x0:Int, y0:Int, x1:Int Var, y1:Int Var)
    x1 = x0 / zoom
    y1 = y0 / zoom
EndFunction



Function PaintNow()
            Print "paint now"
            SetGraphics CanvasGraphics(canvas)
            Local imgX:Int, imgY:Int
            GetImagePosition(imgX, imgY)
            SetClsColor(255, 255, 255)
            Cls
            SetAlpha(1.0)
            SetColor(255, 255, 255)
            SetScale(zoom, zoom)
            DrawImage(tileset, imgX, imgY)
            If leftState
                SetColor(0, 0, 255)
                SetAlpha(0.6)
                SetScale(1.0, 1.0)
                DrawRect(selectX0 * zoom, selectY0 * zoom, (selectX1 - selectX0) * zoom, (selectY1 - selectY0) * zoom)
            EndIf
            Flip
End Function



Function MyHook:Object(id%, data:Object, context:Object)
    Local event:TEvent = TEvent(data)
   
    Select event.source
        Case scrollV , scrollH
            Select Event.Source
                Case scrollH
                    scrollX = Event.Data
                    Print "HOOK EVENT SCROLL H"
                Case scrollV
                    scrollY = Event.Data
            EndSelect
            PaintNow
    End Select
    Return Data
End Function




Here I have a example that selects Tiles by only one click. The SecelectX (MouseClick) needs to be related to ScrollX and imgX to keep at the same place when you zoom:

Import MaxGUI.Drivers

Global GAME_WIDTH = 640
Global GAME_HEIGHT = 480
Global SCROLL_WIDTH = 18

Global window:TGadget = CreateWindow("My Canvas", 100, 100, GAME_WIDTH,GAME_HEIGHT, Null, WINDOW_TITLEBAR | WINDOW_CLIENTCOORDS)

Global canvas:TGadget = CreateCanvas(0, 0, GAME_WIDTH - SCROLL_WIDTH, GAME_HEIGHT - SCROLL_WIDTH, window)
Global scrollV:TGadget = CreateSlider(GAME_WIDTH - SCROLL_WIDTH, 0, SCROLL_WIDTH, GAME_HEIGHT, window, SLIDER_VERTICAL)
Global scrollH:TGadget = CreateSlider(0, GAME_HEIGHT - SCROLL_WIDTH, GAME_WIDTH, SCROLL_WIDTH, window, SLIDER_HORIZONTAL)

Global tileset:TImage = LoadImage("basictiles.png", 0)

SetSliderRange(scrollV, ImageHeight(tileset), GadgetHeight(canvas))
SetSliderRange(scrollH, ImageWidth(tileset), GadgetWidth(canvas))

Global zoom:Float = 4.0

Global ScrollX:Int, ScrollY:Int
Global imgX:Int, imgY:Int
Global SelectX:Int, SelectY:Int

ActivateGadget(canvas)
AddHook EmitEventHook, MyHook
CreateTimer 10

While WaitEvent()
Select EventID()
Case EVENT_TIMERTICK, EVENT_GADGETPAINT
PaintNow()
Case EVENT_KEYDOWN
Select EventData()
Case KEY_DOWN
DoZoomOut()
Case KEY_UP
DoZoomIn()
EndSelect

Case EVENT_MOUSEDOWN
GetTile EventX(), EventY()
Case EVENT_GADGETACTION

Case EVENT_WINDOWCLOSE, EVENT_APPTERMINATE
End
EndSelect
Wend


Function GetTile(X:Int,Y:Int)
Print "imgX=" + imgx + " " + scrollX
SelectX = Int((X-imgX+ScrollX)/zoom/16)*16
SelectY = Int((Y-imgY+ScrollY)/zoom/16)*16
Print "selected " + SelectX + " " + selectY
End Function


Function GetPositions()
Local imgW:Int = ImageWidth(tileset) * zoom
Local imgH:Int = ImageHeight(tileset) * zoom
If imgW < GadgetWidth(canvas)
imgX = (GadgetWidth(canvas) - imgW) / 2
Else
imgX = 0
EndIf
If imgH < GadgetHeight(canvas)
imgY = (GadgetHeight(canvas) - imgH) / 2
Else
imgY = 0
EndIf
imgX:-scrollX
imgY:-scrollY
EndFunction


Function DoZoomIn()
  If zoom = 0.25
zoom:+ 0.25
  ElseIf zoom = 0.50
zoom = 1.0
  ElseIf zoom => 1.0 And zoom <= 7.0
zoom:+ 1.0
  EndIf
GetPositions
EndFunction



Function DoZoomOut()
  If zoom = 0.5
zoom = 0.25
  ElseIf zoom = 1
zoom = 0.50
  ElseIf zoom > 1
zoom:- 1.0
  EndIf
GetPositions
EndFunction



Function PaintNow()
SetGraphics CanvasGraphics(canvas)
SetBlend ALPHABLEND
SetClsColor(255, 255, 255)
Cls
SetAlpha(1.0)
SetColor(255, 255, 255)
SetScale(zoom, zoom)
DrawImage tileset, imgX-ScrollX, imgY-ScrollY
DrawOutLineRect SelectX, SelectY
Flip
End Function



Function DrawOutLineRect(X:Int, Y:Int)
SetColor(0, 0, 255)
SetAlpha(0.6)
DrawRect imgX + (X*zoom)-ScrollX , imgY + (Y*zoom)-ScrollY , 16, 16
End Function


Function MyHook:Object(id%, data:Object, context:Object)
Local event:TEvent = TEvent(data)

Select event.source
Case scrollV , scrollH
Select Event.Source
Case scrollH
scrollX = Event.Data
Print "HOOK EVENT SCROLL H"
Case scrollV
scrollY = Event.Data
EndSelect
PaintNow
End Select
Return Data
End Function

...back from North Pole.

wombats