B4J Question Zoom Image with Mouse Cursor as the Center in ScrollPane

xulihang

Well-Known Member
Licensed User
Longtime User
I embed an ImageView inside a ScrollPane and make it zoomable with the following code:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private Button1 As B4XView
    Private ScrollPane1 As ScrollPane
    Private ImageView1 As ImageView
    Private percentage As Int = 100
    Private CheckBox1 As CheckBox
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    ScrollPane1.LoadLayout("ImageView",600,600)
    ImageView1.SetImage(fx.LoadImage(File.DirApp,"test.png"))
    Dim r As Reflector
    r.Target = ScrollPane1
    r.AddEventFilter("scroll", "javafx.scene.input.ScrollEvent.SCROLL")
End Sub

Sub scroll_Filter (EventData As Event)
    Dim se As JavaObject = EventData
    se.RunMethod("consume", Null)

    Dim DeltaY As Double = se.RunMethod("getDeltaY", Null)
    Dim img As Image = ImageView1.GetImage

    If DeltaY > 0 Then
        percentage = percentage + 5
    Else
        percentage = percentage - 5
    End If

    ImageView1.Width  = img.Width  * percentage / 100
    ImageView1.Height = img.Height * percentage / 100

    ScrollPane1.InnerNode.PrefWidth  = ImageView1.Width
    ScrollPane1.InnerNode.PrefHeight = ImageView1.Height

End Sub

Sub Button1_Click
    xui.MsgboxAsync("Hello World!", "B4X")
End Sub

Now I want to zoom with the mouse cursor as the zoom origin by modifying the event to set the ScrollPane's HPosition and VPosition:

B4X:
Sub scroll_Filter (EventData As Event)
    Dim se As JavaObject = EventData
    se.RunMethod("consume", Null)

    ' ===== 1.  Scene XY =====
    Dim sceneX As Double = se.RunMethod("getSceneX", Null)
    Dim sceneY As Double = se.RunMethod("getSceneY", Null)

    ' ===== 2. scene → innerNode local (before zoom) =====
    Dim innerJO As JavaObject = ScrollPane1.InnerNode
    Dim p As JavaObject = innerJO.RunMethod("sceneToLocal", Array(sceneX, sceneY))
    Dim localX As Double = p.RunMethod("getX", Null)
    Dim localY As Double = p.RunMethod("getY", Null)

    ' ===== 3. zoom =====
    Dim DeltaY As Double = se.RunMethod("getDeltaY", Null)
    Dim img As Image = ImageView1.GetImage

    Dim oldWidth As Double = ImageView1.Width
    Dim oldHeight As Double = ImageView1.Height

    If DeltaY > 0 Then
        percentage = percentage + 5
    Else
        percentage = percentage - 5
    End If

    ImageView1.Width  = img.Width  * percentage / 100
    ImageView1.Height = img.Height * percentage / 100

    ScrollPane1.InnerNode.PrefWidth  = ImageView1.Width
    ScrollPane1.InnerNode.PrefHeight = ImageView1.Height

    If CheckBox1.Checked Then
        ' ===== 4. calculate scale X Y =====
        Dim scaleX As Double = ImageView1.Width / oldWidth
        Dim scaleY As Double = ImageView1.Height / oldHeight

        ' ===== 5. new cursor position in innerNode =====
        Dim newLocalX As Double = localX * scaleX
        Dim newLocalY As Double = localY * scaleY

        ' ===== 6. calculate ScrollPane H/V offsets =====
        Dim contentWidth As Double = ScrollPane1.InnerNode.PrefWidth
        Dim contentHeight As Double = ScrollPane1.InnerNode.PrefHeight

        Dim viewportWidth As Double = ScrollPane1.Width
        Dim viewportHeight As Double = ScrollPane1.Height

        Dim newHPos As Double = (newLocalX - localX + ScrollPane1.HPosition * (contentWidth - viewportWidth)) / (contentWidth - viewportWidth)
        Dim newVPos As Double = (newLocalY - localY + ScrollPane1.VPosition * (contentHeight - viewportHeight)) / (contentHeight - viewportHeight)

        '  0~1
        If newHPos < 0 Then newHPos = 0
        If newHPos > 1 Then newHPos = 1
        If newVPos < 0 Then newVPos = 0
        If newVPos > 1 Then newVPos = 1

        ScrollPane1.HPosition = newHPos
        ScrollPane1.VPosition = newVPos
    End If
End Sub

The result is okay. But it is not accurately using the cursor as the zoom origin. Could someone help me improve the code?

I cannot get a good result using AI.
 

Attachments

  • zoomtest.zip
    20.9 KB · Views: 8

Swissmade

Well-Known Member
Licensed User
Longtime User
I don't know if I understand correct.
You like to Zoom the image in very small steps and if possible in the middle of the ScrollPane.
Is that correct?
 
Last edited:
Upvote 0

xulihang

Well-Known Member
Licensed User
Longtime User
After some investigation, I think this is because of the precision of the calculation.

I updated the project to check the target cursor's X and the real cursor's X to refine the position of the scrollbar.

B4X:
        Dim p As JavaObject = innerJO.RunMethod("sceneToLocal", Array(sceneX, sceneY))
        Dim localX As Double = p.RunMethod("getX", Null)
        Dim localY As Double = p.RunMethod("getY", Null)
        Log("target x: "&newLocalX)
        Log("real x: "&localX)
        Log("target y: "&newLocalY)
        Log("real y: "&localY)
        If newLocalX <> localX Then
            Dim offsetX As Double = newLocalX - localX
            
            Dim newHPos As Double = (offsetX + ScrollPane1.HPosition * (contentWidth - viewportWidth)) / (contentWidth - viewportWidth)
            
            ScrollPane1.HPosition = newHPos
            
        End If
        If newLocalY <> localY Then
            Dim offsetY As Double = newLocalY - localY
            Dim newVPos As Double = (offsetY + ScrollPane1.VPosition * (contentHeight - viewportHeight)) / (contentHeight - viewportHeight)
            ScrollPane1.VPosition = newVPos
        End If
 

Attachments

  • ZoomTest.zip
    412.3 KB · Views: 16
Upvote 0
Top