5
\$\begingroup\$

Situation

I've written a simple black and white image filter application with JavaScript using html5 canvas. I have take two approaches, one with websockets and the other normally, the code is hosted on github

On my desktop browser and Firefox OS, for images of dimensions less than 1024X768 px, the application worked fine and so I posted it on the marketplace. But when I downloaded it on my Android with the Firefox browser, the application was running very very slow even for images 500x500 having size of 100kbs.

Implementation

Basically what I did was, I set the canvas display: hidden, get the image buffer data, take the individual pixel values and modify them (typical point operations), and then generate a .png image from the resulting operation and use an img tag to display it, because I could not control the canvas to fit the user screen without overflowing (for images with dimensions greater than screen dimensions).

Below are fragments of code from the original code

    function $(tagname, classname){
    if(!classname)
        return document.getElementsByTagName(tagname);
    if(!tagname || tagname == '')
        return document.getElementsByClassName(classname);

    return document.querySelectorAll(tagname + '.' + classname);

}

function init(imageObj){
    var ctx, i, data,image,
        len = $('canvas').length;
    parentCanvas = $('canvas')[0];
    parentContext = parentCanvas.getContext('2d');
    if(!parentContext) alert("err");
    w = (imageObj.naturalWidth);
    h = (imageObj.naturalHeight);
    parentCanvas.width = w;
    parentCanvas.height = h;
    parentContext.drawImage(imageObj, 0, 0, w, h);
    parentBuffer = parentContext.getImageData(0, 0, w, h);
    image = document.getElementById("canvas");
    if(!image){
        image = document.createElement('img');
        image.id = "canvas";
    }   
    image.src = parentCanvas.toDataURL("image/png");
    $('','container')[0].appendChild(image);
    //fit();
}
var Effects = {
    '1' : function redfilterbw (brgba) {
        var utils = new Utils();
        var res = utils.monochrome(brgba, 1, 0, 0);
        rgba = {
            r : res,
            g : res,
            b : res,
            a : brgba.a
        };
    },
    '11' : function subcontrast(brgba) {
    var utils = new Utils();
    var res = utils.adjustContrast(brgba,5,-1);
    rgba = {
        r : res.r,
        g : res.g,
        b : res.b,
        a : brgba.a
    };
}, ...
'12' : function addcontrast(brgba) {
    var utils = new Utils();
    var res = utils.adjustContrast(brgba,5,1);
    rgba = {
        r : res.r,
        g : res.g,
        b : res.b,
        a : brgba.a
    };
    };
function Utils () {}

Utils.prototype.monochrome = function (brgba, rwt, gwt, bwt) {
    var opr, scale;
        scale = 1 / (rwt + gwt + bwt);
    rwt *= scale;
    gwt *= scale;
    bwt *= scale;

    opr = parseInt(brgba.r) * rwt + parseInt(brgba.g) * gwt + parseInt(brgba.b) * bwt ;

    return opr;
};
Utils.prototype.adjustContrast = function (brgba, mag, charge) {
    var opr, adjust = mag * charge, //charge is whether image contrast will increase or decrease and mag is magniute, so its like +5 or -5
        factor = (259 * (adjust + 255)) / (255 * (259 - adjust));
    opr = {
        r : (factor * (brgba.r   - 128) + 128),
        g : (factor * (brgba.g   - 128) + 128),
        b : (factor * (brgba.b   - 128) + 128),
        a : brgba.a
    };
    return opr;
};
function fit(){

    document.getElementById("canvas").src = parentCanvas.toDataURL("image/png");
}

function process (index) {
    var i, j, len, data, layer, ctxn, buffer, utils;
    utils = new Utils();
        //the indexes 9 to 12 are for brightness adjustment hence i need the current context whereas rest are independent operations requiring parentContext.
    if (index != 9 && index != 10 && index != 11 && index !=12) {
        ctxn = utils.getParentContext();
    } else {
        ctxn = utils.getthisContext();
    }

    buffer = ctxn.getImageData(0, 0, w, h);
    data = buffer.data;

    for(i = 0; i < data.length; i += 4){
        rgba = {
            r : data[i],
            g : data[i+1],
            b : data[i+2],
            a : data[i+3]
        };
        window['Effects'][index](rgba);
        data[i] = parseInt(rgba.r);
        data[i+1] = parseInt(rgba.g);
        data[i+2] = parseInt(rgba.b);
    }

    ctxn.putImageData(buffer, 0, 0);
    fit();
}

Explanation of attempts

Firstly I don't think, what I have done here is the best way to get the job done, nor maybe the standard way to write a good application.

For the time being I thought I had solution which was to first have preview of the image displayed upon which the users would apply the filter and when they save it the effect would be applied to the original image. But the algorithms I found https://stackoverflow.com/questions/18922880/ were slow, I mean the topmost rated algorithm takes nearly a minute on my pc.

I do not think that approach will work.

Request

I am looking for a review of my code and implementation, I am particularly interested in how it could be improved and sped up. I am also open to new implementation suggestions and conventions on how to handle images in web-applications.

\$\endgroup\$
5
\$\begingroup\$

Interesting question, I wish you had posted more code.

Some observations:

  • Do not use parseInt, use the unary plus so instead of i = parseInt(s) do i = +s; This does not help with readability, but it does help with speed

  • You seem to sanitizing numbers with parseInt over and over again, do it once and then never again ( You should not have to do this in Utils.prototype.monochrome )

  • Read up on lookup tables, you are working on color values ( ranging from 0 - 255 ), it is generally much faster to look up a value in a lookup table than to re-calculate. Something like this ( untested )

    //I assume you initialize Utils.contrastTable prior as { mag : -1 , charge : -1 };
    Utils.prototype.adjustContrast = function (brgba, mag, charge) {
    
        var lookup = this.contrastTable, adjust, factor;
        if( lookup.mag != mag || lookup.charge != charge ){
          //Build the index, loose some time
          adjust = mag * charge;
          factor = (259 * (adjust + 255)) / (255 * (259 - adjust));
          for( var i = 0 ; i < 256 ; i++ )
            lookup[i] = (factor * (i   - 128) + 128);
          lookup.mag = mag;
          lookup.charge = charge;
        }
    
        return {
            r : lookup[brgba.r],
            g : lookup[brgba.g],
            b : lookup[brgba.b],
            a : brgba.a
        };
    };
    
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.