Android Question How to scale canvas without consuming more memory

Discussion in 'Android Questions' started by ZenWhisk, Jun 18, 2015.

  1. ZenWhisk

    ZenWhisk Member Licensed User

    I think this is my first ever question and I have been using B4A for many years now, it is awesome.

    I have an app where the user is presented with an invoice (jpg) to sign. I have a canvas attached to a panel, and the user draws (signs) on it.

    They have now got a new phone with a much higher pixel density, so to keep the same physical size I have to make a bigger destination rectangle because the panel is bigger. The trouble is that canvas.initialize causes out of memory ( and I have a large heap etc)

    so the question is , is there anyway to say that the canvas is to be scaled up by say 50%, but not take any more memory.

    This works on my samsung note 8


    Sub btn_Click
    Dim bmp As Bitmap
    bmp=LoadBitmap(File.DirAssets,"invoice.jpg")
    Dim cvs As Canvas
    Dim destRect As Rect
    destRect.Initialize(0,0,pnl.Width,pnl.Height)
    cvs.Initialize(pnl)
    cvs.DrawBitmap(bmp,Null,destRect)
    End Sub

    but not on my samsung note 4 because it has such a high pixel density that the panel width and height are much bigger.

    the crash happens on cvs.initialize(pnl)

    Note the panel is bigger than the screen, and I'm using the draggable view so the user can drag the panel around to see the whole invoice.
     
  2. RandomCoder

    RandomCoder Well-Known Member Licensed User

    Instead of LoadBitmap to load the invoice you could try LoadBitmapSample... http://www.b4x.com/android/help/core.html#keywords_loadbitmapsample

     
    Peter Simpson likes this.
  3. ZenWhisk

    ZenWhisk Member Licensed User

    Thanks RamdomCoder, I am going to use your tip whenever I play with bitmaps.

    It seems that the bitmap that causes the crash is on that is created by cvs.initialize itself, I modified the code to not even have a bitmap !! Here is the modified code posting:

    Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim btn As Button
    Dim pnl As Panel
    End Sub

    Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
    btn.Initialize("btn")
    Activity.AddView(btn,0,0,300,100)
    pnl.Initialize("pnl")
    Activity.AddView(pnl,0,150,200%x,200%y) 'Panel is bigger than screen the user will scroll
    pnl.Color=Colors.LightGray
    btn.Text="Click"
    End Sub

    Sub btn_Click

    Dim cvs As Canvas
    Dim destRect As Rect
    destRect.Initialize(0,0,pnl.Width,pnl.Height)
    cvs.Initialize(pnl)

    End Sub

    And here is the error trace:

    Installing file.
    PackageAdded: package:b4a.example
    ** Activity (main) Create, isFirst = true **
    ** Activity (main) Resume **
    main_btn_click (B4A line: 44)
    cvs.Initialize(pnl)
    java.lang.OutOfMemoryError
    at android.graphics.Bitmap.nativeCreate(Native Method)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:928)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:901)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:868)
    at anywheresoftware.b4a.objects.drawable.CanvasWrapper.Initialize(CanvasWrapper.java:76)
    at b4a.example.main._btn_click(main.java:421)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:187)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:78)
    at android.view.View.performClick(View.java:4640)
    at android.view.View$PerformClick.run(View.java:19421)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5487)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
    at dalvik.system.NativeStart.main(Native Method)

    So I guess that cvs.initialize creates itself an 'internal' bitmap the same size as the panel and that crashes. The physical size I need for the panel was about 4 inches by 6 inches and at low dpi that was not a big panel measured in px. But on the note 4 with a 500dip screen that was a lot of px so the canvas was too big in px terms.

    If I only wanted to view the jpg then I suppose the webview would do the trick, but it is required that the app user can draw on the canvas.


    Is there anyway to tell the canvas "I know you are 500 dpi but please act as if you were 160dip" ?
     
  4. Erel

    Erel Administrator Staff Member Licensed User

    Yes. Create a canvas that draws on a mutable bitmap (with any size you like). Wrap the bitmap with BitmapDrawable and set the Gravity to Gravity.Fill.

    Set the BitmapDrawable as the panel's Background.
     
  5. ZenWhisk

    ZenWhisk Member Licensed User

    I think I do not understand what 'wrap the bitmap with BitmapDrawable' really means, or the reason. Here is my code is this what you mean? Best Regards zenWhisk

    Code:
    Sub Globals
        
    'These global variables will be redeclared each time the activity is created.
        'These variables can only be accessed from this module.
        Dim cvs As Canvas
        
    Dim btn As Button
        
    Dim pnl As Panel
        
    Dim bmpInvoice As Bitmap
        
    Dim bmpMutableBitmap As Bitmap  
        
    Dim bmpDrawableBitmap As BitmapDrawable  
    End Sub

    Sub Activity_Create(FirstTime As Boolean)
        
    'Do not forget to load the layout file created with the visual designer. For example:
        'Activity.LoadLayout("Layout1")
        btn.Initialize("btn")
        
    Activity.AddView(btn,0,0,300dip,100dip)
        pnl.Initialize(
    "pnl")
        
    Activity.AddView(pnl,0,150dip,200%x,400%y)
        pnl.Color=
    Colors.LightGray
        btn.Text=
    "Click"
        bmpInvoice=
    LoadBitmap(File.DirAssets,"invoice.jpg")  
    End Sub

    Sub btn_Click
        
    Dim destRect As Rect
      
        
    'Scale is a fudge factor so that the picture is the same size on note 8 and note 4
        Dim scale As Float = 100dip/133
      
        
    'Make the panel the right size, if the pixel density is high then we need a bigger panel
        pnl.Width=bmpInvoice.Width*scale
        pnl.Height=bmpInvoice.Height*scale

        destRect.Initialize(
    0,0,pnl.Width,pnl.Height)
      
        
    'Make a mutable bitmap the same size as the panel
        bmpMutableBitmap.InitializeMutable( pnl.Width,pnl.Height)
        bmpDrawableBitmap.Initialize( bmpMutableBitmap)
        bmpDrawableBitmap.Gravity=
    Gravity.FILL
      
        
    'Create a canvas that draws on the mutable bitmap
        cvs.Initialize2(bmpMutableBitmap)
      
        
    'Now paint our invoice on the canvas on the mutable bitmap
        cvs.DrawBitmap(bmpInvoice,Null,destRect)  
        pnl.SetBackgroundImage(bmpDrawableBitmap.Bitmap)
    End Sub
     
    Last edited: Jun 20, 2015
  6. Erel

    Erel Administrator Staff Member Licensed User

    Please use [ code ] [ /code ] tags (without spaces) when posting code.

    Your code looks almost correct. The scale should be smaller than 1.
    Change pnl.SetBackgroundImage to pnl.Background = bmpDrawableBitmap.
     
    ZenWhisk likes this.
Loading...