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: 22

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: 24
Upvote 0

PaulMeuris

Well-Known Member
Licensed User
1765957371812.png
1765957398497.png

Using the code from Erel's ZoomImageView library I managed to put the ImageView's pane in a ScrollPane.
The ScrollPane is pannable which means that you can drag the ImageView pane inside the ScrollPane.
With the scrollwheel from the mouse you can zoom in and out.
You can find the source code in the attachment: testenvironment116.zip
 

Attachments

  • testenvironment116.zip
    410 KB · Views: 16
Upvote 0

xulihang

Well-Known Member
Licensed User
Longtime User
View attachment 168919 View attachment 168920
Using the code from Erel's ZoomImageView library I managed to put the ImageView's pane in a ScrollPane.
The ScrollPane is pannable which means that you can drag the ImageView pane inside the ScrollPane.
With the scrollwheel from the mouse you can zoom in and out.
You can find the source code in the attachment: testenvironment116.zip

I do plan to put HugeImageView inside a ScrollPane as it is more fluent (still needs a lot of memory). But your implementation is not what I desired, the scrollbar length is not changing based on the zoom ratio.
 
Upvote 0
Top