3

I am working on image multi upload, that sounds good, but .. as always memory issue.

Script goal is to survive uploading of 100+ images (300Mb+). So if you will find (i am still javascript lame) any issue, please give me an advice. Thanks.

My code:

CFileReader.prototype.proccessFile = function(cb) {
    // this means File
    var reader = new FileReader();
    reader.readAsDataURL(this);
    reader.onload = (function (f) { 
        return function(e) {
            var image = new Image();
            image.src = e.target.result;
            image.onload = (function(f) {
                return function() {
                    var maxWidth = 700,
                        maxHeight = 700,
                        imageWidth = this.width,
                        imageHeight = this.height;

                    if (imageWidth > imageHeight) {
                      if (imageWidth > maxWidth) {
                        imageHeight *= maxWidth / imageWidth;
                        imageWidth = maxWidth;
                      }
                    }
                    else {
                      if (imageHeight > maxHeight) {
                        imageWidth *= maxHeight / imageHeight;
                        imageHeight = maxHeight;
                      }
                    }
                    var canvas = document.createElement('canvas');
                    canvas.width = imageWidth;
                    canvas.height = imageHeight;

                    var ctx = canvas.getContext("2d");
                    ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
                    if(typeof cb == 'function') {
                        cb(f,canvas.toDataURL());
                    }
                    delete canvas;
                    delete ctx;
                    return;
                }
            })(f);

        };
    })(this);    
}
Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
  • are you uploading to a server? or just using the images client side? just because I don't see any server communication going on.. – lostsource Apr 08 '13 at 22:34
  • I am uploading them. Upload script is in callback, detaching this load script to own scope was a good move, it saved lot of memory. But there are still problem, process thread still commits over 1,2G ram. – Michal Taneček Apr 08 '13 at 22:46
  • Try using memory profiler to see which objects leak memory: http://stackoverflow.com/questions/171565/javascript-memory-profiler-for-firefox I suspect created `` elements are not GC'ed fast enough and there is nothing you can do here. But I assume memory profile is different with systems with less available RAM. If RAM is free the browser consumes it. – Mikko Ohtamaa Apr 09 '13 at 11:45
  • Thanks Mikko for advice. But it is not simple, as you wrote. I`ve detached image async onload event to separated scope, then garbage collector goes frenzy, and keeps thread on useable ram commit value (debian standard overcommit value). It seems, to (function(){}) (scope); is going to be a care for everything in javascript. P.S.: 180 of 200 pictures (1.7GB) survived multiupload .. i think, thats really good result for first alpha version ...\ – Michal Taneček Apr 09 '13 at 20:15

2 Answers2

3

I think window.createObjectURL is faster than FileReader, you should use both with fall back for filereader...also you can check the performance for each operation because there are differences per browser eg http://jsperf.com/canvas-image-resizing and do not forget to revokeObjectURL for memory reasons...also you could yous webworkers http://www.html5rocks.com/en/tutorials/file/filesystem-sync/#toc-readingsync

    if("createObjectURL" in window || "URL" in window && 
    "createObjectURL" in window.URL || "webkitURL" in window && 
    "createObjectURL" in window.webkitURL) { 
        if("createObjectURL" in window) { 
            // Chrome exposes create/revokeObjectURL directly on window 
            objURL = window.createObjectURL(file); 
        } else if("webkitURL" in window) { 
            // Chrome exposes create/revokeObjectURL on the new  webkitURL API 
            objURL = window.webkitURL.createObjectURL(file); 
        } else { 
            // FF4 exposes create/revokeObjectURL on the new URL API 
            objURL = window.URL.createObjectURL(file); 
        } 

        // RESIZE image 
        // STORED IN    
        // objURL
} else { 
    // fallback to FileReader for FF3.6             
    reader = new FileReader();
    reader.onload =  function(event) {                                                                  
        // RESIZE image 
        // STORED IN    
        // event.target.result                 
    }

    reader.onprogress = function (evt) {
       if (evt.lengthComputable) {
            var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
            console.log(percentLoaded);
       }
    }
    reader.readAsDataURL(file);                                                                 
} 
Jinxmcg
  • 1,830
  • 1
  • 21
  • 24
0

Ok then, few days of googling, many hours of finding the best solution. (Not best of ever, but the best one, which i can figgered out). If someone have better, place it s answer, or just give bad reputation.

Goal one: Store everything in global space (canvas,reader,canvas context,image)

window.reader = new FileReader();
window.canvas = document.createElement('canvas');
window.image = new Image();
window.ctx = window.canvas.getContext("2d");

Goal two: Use canvas.toBlob method (implemented only in Firefox at now, you will need some polyfill for that)

Goal three: Add some waitings, This i can't just explain deterministicly, it's only empiric. Goal is, to allow browser, to call garbage collector tick between calls.

So there is my last dev script:

window.prepareFile = function (index,cb) {
    // this means file html element
    var res = window.configuration.photo.resolution.split(":");

    //var res = [300,200];
    var reader = window.reader;
    $('#fm-up-status > tr[for="'+this.files[index].name+'"]').children('td')[3].innerHTML = 'Zpracovávám';
    reader.readAsDataURL(this.files[index]);
    reader.onload = (function (f) { 
        return function(e) {
            setTimeout(function(){
                var image = window.image;
                image.src = e.target.result;
                image.onload = (function(f) {
                    return function() {
                        var maxWidth = parseInt(res[0]),
                            maxHeight = parseInt(res[1]),
                            imageWidth = this.width,
                            imageHeight = this.height;
                        if (imageWidth > imageHeight) {
                            if (imageWidth > maxWidth) {
                              imageHeight *= maxWidth / imageWidth;
                              imageWidth = maxWidth;
                            }
                        }
                        else {
                            if (imageHeight > maxHeight) {
                              imageWidth *= maxHeight / imageHeight;
                              imageHeight = maxHeight;
                            }
                        }
                        var canvas = window.canvas;
                        canvas.width =0;
                        canvas.width = imageWidth;
                        canvas.height = imageHeight;
                        window.ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
                        canvas.toBlob(
                            function (blob) {
                                var formData = new FormData();

                                formData.append('file', blob, f.name);
                                cb(f.name,formData);
                            },
                            'image/jpeg'
                        );
                        return;
                    }
                })(f);
                return;
            },500);
        };
    })(this.files[index]);

    return;
}

Then you can simple upload data as normal form (multipar), like this:

window.uploader = function(filename,formdata,cb) {

    $('#fm-up-status > tr[for="'+filename+'"]').children('td')[3].innerHTML = 'Uploaduji';
    xhr = jQuery.ajaxSettings.xhr();
    if (xhr.upload) {
        xhr.upload.addEventListener('progress', function (evt) {
            if (evt.lengthComputable) {  
                var floatComplete = evt.loaded / evt.total;
                window.uploadProgress(filename,floatComplete);

            }
        }, false);
    }  
    provider = function () {
        return xhr;
    };    
    $.ajax({
        url: window.uploadUri,
        type: "POST",
        data: formdata,
        xhr: provider,
        processData: false,
        contentType: false,
        dataType: "json",
        success: function (res,state,xhr) {
            cb(false,res,xhr);
            return;
        },
        error: function (xhr, state, err) {
            cb(err,false,xhr)
            return;
        }
    });    
};

This code is really not production ready, it is a simple proof-of-concept

PSWai
  • 1,198
  • 13
  • 32