B4A Library ScaleImageView - Pan and zoom large images

Discussion in 'Additional libraries, classes and official updates' started by agraham, Feb 1, 2019.

  1. agraham

    agraham Expert Licensed User

    My main reason to restart using B4A was to implement on a mobile device my mapping program which covers the entire UK at various scales and requires the ability to display large map images at a specified zoom level and centered on a specified coordinate and then allow the user to drag and zoom the map.

    For some strange reason if you set targetSdkVersion="xx" to more than 13 in the manifest it dramatically reduces the largest Bitmap that can be created from a file so most of my map images then no longer open in TouchImageView, jsTouchImageView and even ImageView. These all rely on opening a full resolution bitmap to display an image. It seems to be a Canvas size limitation. Also I failed to get TouchImageView to reliably pre-zoom and pre-position an image so I struggled on with jsTouchImageView and targetSdkVersion="13" and got an acceptable working version but pre-zooming and pre-positioning at an exact point was a bit problematic and not entirely accurate but usually close enough after a few heuristics. It also still used very large amounts of memory for the decoded bitmap.

    Having got the app working I then started looking to try to finally solve the image viewing problems and found some source code on GitHub that was written for just this purpose. Instead of loading the entire image at once it only fully decodes from the image file the areas needed for display. As a bonus the pre-zooming and centering is very accurate and works a treat. So after a bit of difficulty getting it to compile I wrapped it, slightly enhanced to draw a centered point indication and am now extremely happy with my mapping program that can now display huge images and target SDK version 26 and later without using a vast amount of memory.

    There is a ScaleImageView.htm file in the attached archive that documents the extremely simple API posted below. As always do what readme.txt says.

    I can't put a large image file in the archive as it becomes too big to upload here, so find your own large image and do as Readme.txt says.

    ScaleImageView
    This is a custom image view designed for displaying huge images.
    It includes all the standard gestures for zooming and panning images
    and provides some extra useful features for animating the image position and scale.
    The aim of this library is to solve some of the common problems when displaying large images in Android.
    This view extends View and so inherits all the normal View methods.
    This view doesn't extend ImageView and isn't intended as a general purpose replacement for it.
    It is specialised for the display of photos and other large images, not the display of 9-patches,
    shapes and the other types of drawable that ImageView supports.
    Supported gestures are:
    One finger drag to pan, Two finger pinch to zoom and double tap to zoom in and out.
    Pan while zooming, seamless switch between pan and zoom and fling momentum after panning.
    Quick scale (one finger zoom - quick double tap then drag)
    Events Click and LongClick are provided whose co-ordinates may be accessed in the event code.
    ClickViewX, ClickViewY return the position of the Click or LongClick on the view.
    ClickImageX and ClickImageY return the position of the Click or LongClick on the source image.
    The OnDraw event provides a Canvas that can be used to draw on the view whenever it is redrawn.
    The view can draw a circle at a defined position on the original image.
    The circle can either have a fixed size that is a fraction of the screen width
    or a variable size that always covers the same area of the original size as it is zoomed.
    The associated file, ScaleImage.jar, must be located in the Additional Libraries folder.
    The line '#AdditionalJar: ScaleImage' must be added to the Main module.
    This library uses code from
    https://github.com/davemorrissey/subsampling-scale-image-view
    That code and this library are licensed under the Apache License 2.0
    Copyright [2015] [Dave Morrissey]
    Copyright [2018] [Andrew Graham]
    Licensed under the Apache License, Version 2.0 (the "License")
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    Author: Andrew Graham
    Version: 2.0
    • ScaleImageView
      • Events:
        • Click 'The user has tapped on the view. Use ClickImage or ClickView for the coordinates.
        • LongClick 'The user has long pressed the view. Use ClickImage or ClickView for the coordinates.
        • OnDraw (viewcanvas As Canvas) 'The view is being redrawn. Use viewcanvas to draw on it.
      • Functions:
        • BringToFront
        • DesignerCreateView (base As Panel, lw As Label, props As Map)
        • Initialize (arg1 As String)
        • Invalidate
        • Invalidate2 (arg0 As android.graphics.Rect)
        • Invalidate3 (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
        • IsInitialized As Boolean
        • RemoveView
        • RequestFocus As Boolean
        • SendToBack
        • SetBackgroundImage (arg0 As android.graphics.Bitmap) As BitmapDrawable
        • SetColorAnimated (arg0 As Int, arg1 As Int, arg2 As Int)
        • SetLayout (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int)
        • SetLayoutAnimated (arg0 As Int, arg1 As Int, arg2 As Int, arg3 As Int, arg4 As Int)
        • setMaxZoom As Float
          Gets or set the maximum permitted zoom level of the displayed image. The default is 2.0.
          The minimum zoom level is set automatically to fit the entire image to the view.
        • SetScaleAndCenter (scale As Float, x As Float, y As Float, duration As Int)
          Set the zoom of the displayed image and center it on the point (x,y).
          x and y are width and height factor values of the full image size between 0 and 1.
          PanLimit should be set to PAN_LIMIT_CENTER if any point is to be centered.
          Duration sets the duration of the transition in milliseconds.
          Returns False if the image is not ready and the transition was not made otherwise True.
        • SetScaleAndCenterPixels (scale As Float, x As Int, y As Int, duration As Int) As Boolean
          Set the zoom of the displayed image and center it on the point (x,y).
          x and y are full image size x and y pixel values.
          PanLimit should be set to PAN_LIMIT_CENTER if any point is to be centered.
          Duration sets the duration of the transition in milliseconds.
          Returns False if the image is not ready and the transition was not made otherwise True.
        • SetVisibleAnimated (arg0 As Int, arg1 As Boolean)
        • SourceXYtoViewXY (sourcex As Int, sourcey As Int) As Float()
          Returns a Float array containing the X and Y pixel position on the view of the specified point of the full size image.
          If the image coordinates are currently off screen, the view coordinates will also be outside the view
          Index 0 of the array contains the X coordinate and index 1 the Y coordinate.
        • ViewXYtoSourceXY (viewx As Int, viewy As Int) As Float()
          Returns a Float array containing the X and Y pixel position on the full size image of the specified point of the view.
          Index 0 of the array contains the X coordinate and index 1 the Y coordinate.
      • Properties:
        • Background As android.graphics.drawable.Drawable
        • CenterX As Float [read only]
          Gets the X pixel value of the full image point which is at the centre of the view.
          Can only be set programatically by SetScaleAndCenter or SetScaleAndCenterPixels
        • CenterY As Float [read only]
          Gets the Y pixel value of the full image point which is at the centre of the view.
          Can only be set programmatically by SetScaleAndCenter or SetScaleAndCenterPixels
        • CircleColor As Int
          Gets or sets the colour of the inner ring of the circle.
          The default is 0xff20b2aa - LightSeaGreen.
        • CircleDrawnRadius As Float [read only]
          Gets the radius of the circle in pixels as last drawn regardless of the state of EnableCircleScale.
          This can be used to position other drawn items relative to the circle.
        • CircleMinimumRadius As Float
          Gets or sets the minimum radius of the circle as a factor between 0 and 1 of the width of the view.
          This prevents the circle being drawn vanishingly small when zoomed out if EnableCircleScale is True.
          The default is 0.02.
        • CircleRadius As Float
          Gets or sets the radius of the circle as a factor between 0 and 1 of the width of the view or of the full image.
          If EnableCircleScale is True the factor is that of the full size image width.
          If EnableCircleScale is False the factor is that of the width of the view.
          The default is 0.002 which is appropriate if EnableCircleScale is True.
        • CircleWidth As Float
          Gets or sets the width of stroke used to draw the circle (not the radius).
          The value is a factor between 0 and 1 of the radius of the circle as drawn. The default is 0.2.
        • CircleX As Float
          Gets or sets the X position factor of the point on the full image at which to draw the circle.
          The default is 0.5, the centre of the image.
        • CircleXPixels As Float
          Gets or sets the X position in pixels of the point on the full image at which to draw the circle.
          The default is the centre of the image.
        • CircleY As Float
          Gets or sets the Y position factor of the point on the full image at which to draw the circle.
          The default is 0.5, the centre of the image.
        • CircleYPixels As Float
          Gets or sets the Y position in pixels of the point on the full image at which to draw the circle.
          The default is the centre of the image.
        • ClickImageX As Float [read only]
          When accessed in a Click or Longclickevent gets the X pixel position on the full size image of the point clicked.
        • ClickImageY As Float [read only]
          When accessed in a Click or LongClick event gets the Y pixel position on the full size image of the point clicked.
        • ClickViewX As Float [read only]
          When accessed in a Click or LongClick event gets the X pixel position on the view of the point clicked.
        • ClickViewY As Float [read only]
          When accessed in a Click or LongClick event gets the Y pixel position on the view of the point clicked.
        • Color As Int [write only]
        • EnableCircle As Boolean
          Gets or sets whether a circle will be drawn at the image coordinates specified by CircleX and CircleY.
          The default is False.
        • EnableCircleScale As Boolean
          Gets or sets whether the size of the circle will increase and decrease when the image is zoomed.
          If True the circle will resize to bound the same features on the image whatever the zoom.
          If False the circle will maintain a fixed size on the screen of the device.
          The default is True.
        • Enabled As Boolean
        • Height As Int
        • Image As android.graphics.Bitmap [write only]
          Load an existing Bitmap into the view.
          This is unsuitable for large images because it bypasses subsampling and may cause OutOfMemoryErrors.
          This is an easy way to add pan and zoom functionality to an image already in memory.
        • ImageFile As String [write only]
          Load an image from a file saved on the device file system into the view.
          This method can display JPG and PNG images of any size.
          In order to support huge images without running out of memory, a sub-sampled base layer is first loaded.
          Higher resolution tiles are loaded for the visible area as the user zooms in.
        • IsReady As Boolean [read only]
          Gets whether the view is initialised, has dimensions and will display an image.
          Returns True if the view is ready to display an image and accept touch gestures.
        • Left As Int
        • MaxZoom As Float [write only]
        • Padding As Int()
        • PAN_LIMIT_CENTER As Int [read only]
          Allow the image to be panned until a corner reaches the center of the screen but no further.
          Useful when you need to pan any spot on the image to the exact center of the screen.
        • PAN_LIMIT_INSIDE As Int [read only]
          Don't allow the image to be panned off screen.
          As much of the image as possible is always displayed, centered in the view when it is smaller.
        • PAN_LIMIT_OUTSIDE As Int [read only]
          Allow the image to be panned until it is just off screen, but no further.
          The edge of the image will stop when it is flush with the screen edge.
        • PanLimit As Int [write only]
          Sets the image pan limit to one of the PAN_LIMIT values.
          The default is 3 = PAN_LIMIT_CENTER.
        • Parent As Object [read only]
        • Scale As Float [read only]
          Get the current scale of the image as set by the user.
          Can only be set programmatically by SetScaleAndCenter or SetScaleAndCenterPixels.
        • SrcHeight As Int [read only]
          Get the height of the current full size image in pixels.
        • SrcWidth As Int [read only]
          Get the width of the current full size image in pixels.
        • Tag As Object
        • Top As Int
        • Visible As Boolean
        • Width As Int


        EDIT: Version 2.00 now posted. ScaleImageView is now a Custom View. The API above has been updated. See Post #3 below for more details.
     

    Attached Files:

    Last edited: Mar 8, 2019
  2. Almora

    Almora Active Member Licensed User

    14600x11000
    tested on android 4.2. works great.
     
  3. agraham

    agraham Expert Licensed User

    Version 2.00 is a significant upgrade to ScaleImageView.

    ScaleImageView is now a Custom View and can be added from the Designer. The intrinsic circle drawing API has been refined.

    Being inherited from the View class ScaleImageView always supported Click and LongClick events but now has the annotations in the library for B4A to recognise this. As Click and LongClick events do not support parameters, and the other gesture events are used internally by ScaleImageView, the ClickImage and ClickView properties can be used within event code to locate the coordinates of either event.

    As the image in ScaleImageView is not decoded into a Bitmap (that is the whole raison d'etre for its existence) it is not possible to draw on a large image to annotate it. Now an OnDraw event has been added which supplies a Canvas that allows drawing onto the ScaleImageView. Methods and properties are provided to synchronise drawing on the view with the displayed image. Although the drawn annotations cannot be saved as a new image it is feasible to save and recall annotations to a file that can be associated with the large image file so making it appear as though the annotations were added to the actual image.
     
    Last edited: Mar 8, 2019
  4. agraham

    agraham Expert Licensed User

    A thought about annotating an image. ScaleImageView can display a conventional Bitmap by assigning it to the Image property. I haven't tried this but it should be possible to draw on this Bitmap and save it as a new image with the annotations. However such annotations will scale with the image when displayed. Maintaining the annotations separately and drawing them directly on the view allows them to be scaled independently of the image.
     
    Jaames likes this.
  5. Rainer@B4A

    Rainer@B4A Member Licensed User

    I start testing this lib, but got the following compiler error message:

    B4A Version: 7.30
    Parse den Code. (0.00s)
    Kompiliere den Code. (0.05s)
    Kompiliere Layoutcode. (0.00s)
    Organisiere Libraries. (0.00s)
    Generiere R Datei. (0.03s)
    Kompiliere Debugger-Code (0.69s)
    Kompiliere generierten Java Code. Error
    B4A line: 54
    Activity.AddView(ScaleImageView1, 0%y, 1%y, 100%x
    javac 1.8.0_151
    src\b4a\example\main.java:394: error: cannot access SubsamplingScaleImageView
    mostCurrent._activity.AddView((android.view.View)(mostCurrent._scaleimageview1.getObject()),anywheresoftware.b4a.keywords.Common.PerYToCurrent((float) (0),mostCurrent.activityBA),anywheresoftware.b4a.keywords.Common.PerYToCurrent((float) (1),mostCurrent.activityBA),anywheresoftware.b4a.keywords.Common.PerXToCurrent((float) (100),mostCurrent.activityBA),anywheresoftware.b4a.keywords.Common.PerYToCurrent((float) (98),mostCurrent.activityBA));
    ^
    class file for com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView not found
     
  6. agraham

    agraham Expert Licensed User

    Make sure you have ScaleImage.jar in your Additional Libraries folder as well as ScaleImageView.jar and ScaleImageView.xml.

    Also make sure you have
    #AdditionalJar: ScaleImage
    in Main.
     
  7. Rainer@B4A

    Rainer@B4A Member Licensed User

    I did not recognized "#AdditionalJar: ScaleImage" in your example.
    Now it seems to work perfect.
    Many Thanks
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice