Unable to take many pictures with camera: Failed to allocate a 1553380 byte

plugins

#1

I’m using Nativescript with Angular and the object of the app is to take photos and then send them via email.
I’m using This GridView to display captured images to the user as you can see in the image below:


Now as you can see this is the virtual emulator and the app works fine but when I try the app on a real device after the third picture I get this error:

System.err: com.tns.NativeScriptException:
System.err: Calling js method onBindViewHolder failed
System.err: Error: java.lang.OutOfMemoryError: Failed to allocate a 1553380 byte allocation with 57537 free bytes and 56KB until OOM

I’m using databinding to bind the photos array in the GridView as described in the code:

> takePicture(){
>         camera.takePicture()
>             .then((imageAsset) => {
>                 console.log('Result is an image asset instance');
>                 // convert ImageAsset to ImageSource
>                 fromAsset(imageAsset)
>                     .then(res => {
>                         let base64 = res.toBase64String('png');
>                         let newImage = 'data:image/png;base64,'+base64;
>                         this.photos.push(newImage);// I get the base64 of the picture and save it in the array
>                     })
>             })
>     }

And this is how i’m binding the array in the GridView:

> <GridView [items]="photos" colWidth="30%" rowHeight="100">
>             <ng-template let-item="item" let-odd="odd">
>                 <StackLayout margin="5" borderColor="gray" borderWidth="1" borderRadius="5" verticalAlignment="stretch" class="list-group-item">
>                     <Image [src]="item" ></Image>
>                 </StackLayout>
>             </ng-template>
>         </GridView>

As I said, the app works fine on the emulator no matter how many pictures I take, but when using a real device I get this error after the third picture.
What is the explanation of this behaviour ?


#2

A simple solution could be adding hardwareAccelerated & largeHeap attributes to your application tag within your AndroidManifest.xml.

You may also try compressing the images if that works for your scenario.


#3

Thanks for your answer
How can add this to AndroidManifest.xml
And is there a plugin I can use for compressing images?
Thanks in advance


#4

Your app manifest file can be found at App_Resources/Android/AndroidManifest.xml

<application android:name="com.tns.NativeScriptApplication" 
        android:hardwareAccelerated="true"
        android:largeHeap="true">

Android memory error with images
#5

Thanks a lot I will do it
but is there a way to shrink the image size so I can display it in the GridView and in the same time save the real image in array so I can send it via email?
This way I can reduce the memory leak for display and send the real images via email.


#6

I’m facing the same problem with big images, no luck yet =(, even i’m using fresco lib, best practices of nativescript says that is handled but is not =/.

about the shrink, you can use this

    const fs = require("file-system");
    const folder = fs.knownFolders.documents().path;
    const fileName = "imagename.png";
    const path = fs.path.join(folder, fileName);
    const saved = source.saveToFile(path, "png", 50);

this will compress the png image at 50% if you want to use a jpg image you need to use “jpeg”


#7

Thank @jcasanova but I’m using an imageAsset (image captured from camera) and it’s not saved in the device.
is it possible to shrink an imageAsset ?


#8

I’m working in that point I need it too…


#9

I just did the code, My example, I generated the thumbnail (T) and a image normal a little bit compressed.

var camera = require("nativescript-camera");
var imageSourceModule = require("image-source");

camera.takePicture({ width: 800, height: 600, keepAspectRatio: true, saveToGallery: false }).
        then(function (imageAsset) {
            var source = new imageSourceModule.ImageSource();
            source.fromAsset(imageAsset).then(function (source) {
                var folder = fs.knownFolders.documents().path;
                console.log('folder - ', folder); // => object
                var fileName = 'img_' + new Date().getTime() + '.' + "jpg";
                 var path = fs.path.join(folder, "T-"+fileName);
                 var path2 = fs.path.join(folder, fileName);
                
                var saved = source.saveToFile(path, "jpeg",40);
                if (saved) {
                    console.log("saved image: " + path);
                }
                var saved = source.saveToFile(path2, "jpeg",95);
                if (saved) {
                    console.log("saved image:" +  path2);
                }
                console.log("Size: " + source.width + "x" + source.height);
            });

    }, function (err) {
        console.log("Error -> " + err.message);
    });

#10

Great work, thanks a lot :blush::blush:


#11

as info, I had to use bitmapfactory in order to get a thumb with desired dimension, the saveToFile just compress the image so… you get same file dimensions but less file size… with bitmapfactory I get an thumb of 300x300 …

If you need the code I’ll happy to share.


#12

Yes please, if this does not bother you :slight_smile:


#13

This code, generate an image in order to get thumb for 800, then the result image is compressed and used, the first 800x800 is deleted. Maybe you should try rising the 30 to 60 for better quality in imagesource.saveToFile(path1, "jpeg",30);

var BitmapFactory = require("nativescript-bitmap-factory");

camera.takePicture({ width: 800, height: 600, keepAspectRatio: true, saveToGallery: false }).
        then(function (imageAsset) {
            var source = new imageSourceModule.ImageSource();
            source.fromAsset(imageAsset).then(function (imagesource) {
                var folder = fs.knownFolders.currentApp().path;
                console.log('folder - ', folder); // => object
                try{
                    var fileName = 'img_' + new Date().getTime() + '.' + "jpg";
                    var path = fs.path.join(folder, "T-"+fileName);
                    var path1 = fs.path.join(folder, "TT-"+fileName);
                    var path2 = fs.path.join(folder, fileName);
                    
                    // uncomment for testing
                    //var path = "/storage/emulated/0/Download/T-"+fileName;
                    //var path2 = "/storage/emulated/0/Download/"+fileName;
                    
                    var saved = imagesource.saveToFile(path1, "jpeg",30);
                    if (saved) {
                        console.log("saved image: " + path1);
                    }
                    var saved = imagesource.saveToFile(path2, "jpeg",95);
                    if (saved) {
                        console.log("saved image:" +  path2);
                    }

                    var myImage = imageSourceModule.fromFile(path1);
                    var bmp1 = BitmapFactory.makeMutable(myImage);
                    var bmp = BitmapFactory.asBitmap(bmp1);
                    
                    bmp.dispose(function(b) {
                        // ## Max dimension. Respects aspect ratio.
                        var b2 = bmp.resizeMax(800);
                        var thumb_image = b2.toImageSource();
                        let saved = thumb_image.saveToFile(
                            path,
                            "jpeg",
                            30
                        );
                        if (saved) {
                            console.log("Size T: " + b2.width + "x" + b2.height);
                            console.log("deleting:" + path1);
                            var file = fs.File.fromPath(path1);
                            file.remove()
                                .then(function (result) {
                                    console.log("Remove OK");
                                    imgtempo.imageUri = path;  // <--- HERE is sent to the image tag in view
                                }, function (error) {
                                    console.log("Remove FAILED!!!");
                                });
                            // ## Use resized image
                            console.log("saved!")
                        }
                    });
                    console.log("Size: " + imagesource.width + "x" + imagesource.height);
                }catch(err){
                    console.log("error:" + err)
                }
            });
        // page.bindingContext.set("cameraImage", imageAsset);
    }, function (err) {
        console.log("Error -> " + err.message);
    });

Enjoy!


#14

Thanks a lot man I really appreciate it :slight_smile:


#15

your welcome, I edited an added a comment where the image is returned to the view