Android Question How to scale canvas without consuming more memory

ZenWhisk

Member
Licensed User
Longtime 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.
 

RandomCoder

Well-Known Member
Licensed User
Longtime User
Instead of LoadBitmap to load the invoice you could try LoadBitmapSample... https://www.b4x.com/android/help/core.html#keywords_loadbitmapsample

The decoder will subsample the bitmap if MaxWidth or MaxHeight are smaller than the bitmap dimensions.
This can save a lot of memory when loading large images.
Example:
Activity.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "SomeFile.jpg", Activity.Width, Activity.Height))
 
Upvote 0

ZenWhisk

Member
Licensed User
Longtime 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" ?
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Is there anyway to tell the canvas "I know you are 500 dpi but please act as if you were 160dip" ?
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.
 
Upvote 0

ZenWhisk

Member
Licensed User
Longtime 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

B4X:
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:
Upvote 0
Top