0

I have some strange scope issue in Coffeescript.

I can't access to _this from the function @qr.callback, the _this doesn't seem to pass well. I never change this.imgName so the only reason that It doesn't work could be that _this is'nt passed well.

decode:(@callback) ->
    _this= this
    console.log 'before',_this.imgName
    @qr= new QrCode()
    @qr.callback= () ->
        console.log "after:", _this.imgName
    @qr.decode("data:image/png;base64,#{@base64Data}")

Console Output

Edit:

I have tried using

    console.log 'before',@imgName
    @qr= new QrCode()

    @qr.callback= () =>
        console.log "after:", @imgName
    @qr.decode("data:image/png;base64,#{@base64Data}")

But the output is the same

Edit2: QrCode code: This code comes from https://github.com/LazarSoft/jsqrcode. Howewer, as the source code of LazarSoft https://github.com/LazarSoft/jsqrcode/blob/master/src/qrcode.js did'nt contain a QrCode object that you could instantiate many times , I transformed the code to create many different instances of QrCode by creating a QrCode function instead of a global object qrcode.

QrCode= function ()
  {
    this.imagedata = null;
    this.width = 0;
    this.height = 0;
    this.qrCodeSymbol = null;
    this.debug = false;

this.sizeOfDataLengthInfo =  [  [ 10, 9, 8, 8 ],  [ 12, 11, 16, 10 ],  [ 14, 13, 16, 12 ] ];

this.callback = null;

this.decode = function(src){

    if(arguments.length==0)
    {
        var canvas_qr = document.getElementById("qr-canvas");
        var context = canvas_qr.getContext('2d');
        this.width = canvas_qr.width;
        this.height = canvas_qr.height;
        this.imagedata = context.getImageData(0, 0, this.width, this.height);
        this.result = this.process(context);
        if(this.callback!=null)
            this.callback(this.result);
        return this.result;
    }
    else
    {
        var image = new Image();
        _this=this
        image.onload=function(){
            //var canvas_qr = document.getElementById("qr-canvas");
            var canvas_qr = document.createElement('canvas');
            var context = canvas_qr.getContext('2d');
            var canvas_out = document.getElementById("out-canvas");
            if(canvas_out!=null)
            {
                var outctx = canvas_out.getContext('2d');
                outctx.clearRect(0, 0, 320, 240);
                outctx.drawImage(image, 0, 0, 320, 240);
            }
            canvas_qr.width = image.width;
            canvas_qr.height = image.height;
            context.drawImage(image, 0, 0);
            _this.width = image.width;
            _this.height = image.height;
            try{
                _this.imagedata = context.getImageData(0, 0, image.width, image.height);
            }catch(e){
                _this.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!";
                if(_this.callback!=null)
                    _this.callback(_this.result);
                return;
            }

            try
            {
                _this.result = _this.process(context);
            }
            catch(e)
            {
                // console.log('error:'+e);
                _this.result = "error decoding QR Code";
            }
            if(_this.callback!=null)
                _this.callback(_this.result);
        }
        image.src = src;
    }
}

this.decode_utf8 = function ( s )
{
  return decodeURIComponent( escape( s ) );
}

this.process = function(ctx){

    var start = new Date().getTime();

    var image = this.grayScaleToBitmap(this.grayscale());
    //var image = this.binarize(128);

    if(this.debug)
    {
        for (var y = 0; y < this.height; y++)
        {
            for (var x = 0; x < this.width; x++)
            {
                var point = (x * 4) + (y * this.width * 4);
                this.imagedata.data[point] = image[x+y*this.width]?0:0;
                this.imagedata.data[point+1] = image[x+y*this.width]?0:0;
                this.imagedata.data[point+2] = image[x+y*this.width]?255:0;
            }
        }
        ctx.putImageData(this.imagedata, 0, 0);
    }

    //var finderPatternInfo = new FinderPatternFinder().findFinderPattern(image);

    var detector = new Detector(image,this);

    var qRCodeMatrix = detector.detect();

    /*for (var y = 0; y < qRCodeMatrix.bits.Height; y++)
    {
        for (var x = 0; x < qRCodeMatrix.bits.Width; x++)
        {
            var point = (x * 4*2) + (y*2 * this.width * 4);
            this.imagedata.data[point] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
            this.imagedata.data[point+1] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
            this.imagedata.data[point+2] = qRCodeMatrix.bits.get_Renamed(x,y)?255:0;
        }
    }*/
    if(this.debug)
        ctx.putImageData(this.imagedata, 0, 0);

    var reader = Decoder.decode(qRCodeMatrix.bits,this);
    var data = reader.DataByte;
    var str="";
    for(var i=0;i<data.length;i++)
    {
        for(var j=0;j<data[i].length;j++)
            str+=String.fromCharCode(data[i][j]);
    }

    var end = new Date().getTime();
    var time = end - start;
    console.log(time);

    return this.decode_utf8(str);
    //alert("Time:" + time + " Code: "+str);
}

this.getPixel = function(x,y){
    if (this.width < x) {
        throw "point error";
    }
    if (this.height < y) {
        throw "point error";
    }
    point = (x * 4) + (y * this.width * 4);
    p = (this.imagedata.data[point]*33 + this.imagedata.data[point + 1]*34 + this.imagedata.data[point + 2]*33)/100;
    return p;
}

this.binarize = function(th){
    var ret = new Array(this.width*this.height);
    for (var y = 0; y < this.height; y++)
    {
        for (var x = 0; x < this.width; x++)
        {
            var gray = this.getPixel(x, y);

            ret[x+y*this.width] = gray<=th?true:false;
        }
    }
    return ret;
}

this.getMiddleBrightnessPerArea=function(image)
{
    var numSqrtArea = 4;
    //obtain middle brightness((min + max) / 2) per area
    var areaWidth = Math.floor(this.width / numSqrtArea);
    var areaHeight = Math.floor(this.height / numSqrtArea);
    var minmax = new Array(numSqrtArea);
    for (var i = 0; i < numSqrtArea; i++)
    {
        minmax[i] = new Array(numSqrtArea);
        for (var i2 = 0; i2 < numSqrtArea; i2++)
        {
            minmax[i][i2] = new Array(0,0);
        }
    }
    for (var ay = 0; ay < numSqrtArea; ay++)
    {
        for (var ax = 0; ax < numSqrtArea; ax++)
        {
            minmax[ax][ay][0] = 0xFF;
            for (var dy = 0; dy < areaHeight; dy++)
            {
                for (var dx = 0; dx < areaWidth; dx++)
                {
                    var target = image[areaWidth * ax + dx+(areaHeight * ay + dy)*this.width];
                    if (target < minmax[ax][ay][0])
                        minmax[ax][ay][0] = target;
                    if (target > minmax[ax][ay][1])
                        minmax[ax][ay][1] = target;
                }
            }
            //minmax[ax][ay][0] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
        }
    }
    var middle = new Array(numSqrtArea);
    for (var i3 = 0; i3 < numSqrtArea; i3++)
    {
        middle[i3] = new Array(numSqrtArea);
    }
    for (var ay = 0; ay < numSqrtArea; ay++)
    {
        for (var ax = 0; ax < numSqrtArea; ax++)
        {
            middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
            //Console.out.print(middle[ax][ay] + ",");
        }
        //Console.out.println("");
    }
    //Console.out.println("");

    return middle;
}

this.grayScaleToBitmap=function(grayScale)
{
    var middle = this.getMiddleBrightnessPerArea(grayScale);
    var sqrtNumArea = middle.length;
    var areaWidth = Math.floor(this.width / sqrtNumArea);
    var areaHeight = Math.floor(this.height / sqrtNumArea);
    var bitmap = new Array(this.height*this.width);

    for (var ay = 0; ay < sqrtNumArea; ay++)
    {
        for (var ax = 0; ax < sqrtNumArea; ax++)
        {
            for (var dy = 0; dy < areaHeight; dy++)
            {
                for (var dx = 0; dx < areaWidth; dx++)
                {
                    bitmap[areaWidth * ax + dx+ (areaHeight * ay + dy)*this.width] = (grayScale[areaWidth * ax + dx+ (areaHeight * ay + dy)*this.width] < middle[ax][ay])?true:false;
                }
            }
        }
    }
    return bitmap;
}

this.grayscale = function(){
    var ret = new Array(this.width*this.height);
    for (var y = 0; y < this.height; y++)
    {
        for (var x = 0; x < this.width; x++)
        {
            var gray = this.getPixel(x, y);

            ret[x+y*this.width] = gray;
        }
    }
    return ret;
}

  }
edi9999
  • 19,701
  • 13
  • 88
  • 127
  • Works fine for me. Did you have a `_this` at a higher scope? What's wrong with the log, I can't see an error there? What part of your code calls the `qr.callback`? – Bergi Jul 03 '13 at 14:30
  • There's no _this at a higher scope. The error is that after contains always Copie_9.png, instead of all the numbers in before, meaning they should be before 1, after 1, before 2, after 2, before 3,after 3, before 4, after 4, before 5, after 5. Now they are many after 9.`qr.decode` calls `qr.callback` at the end of the function – edi9999 Jul 03 '13 at 14:36
  • Ah, that's a "*7 times* `after: Copie_9`" - didn't got that. Could you show us the `QrCode` source, please? – Bergi Jul 03 '13 at 14:39
  • I have updated my POST. When I add a `debugger` in the `this.decode` function: like this: `debugger; var image = new Image(); _this=this`, I don't get this error – edi9999 Jul 03 '13 at 15:01

1 Answers1

1
 var image = new Image();
 _this=this

May lightning strike you, you're coding CoffeeScript too much! You forgot a var declaration here, so the ninth invocation of decode overwrites the global _this with its this instance - and when each decoding is finished, they all call the same callback.

Fix it by using

var _this = this;

or

var image = new Image,
    _this = this;

or by using CoffeeScript everywhere :-)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • So when not declaring `_this` with a `var`, the scope of `_this` is `window` ? – edi9999 Jul 04 '13 at 10:41
  • 1
    Yes, see [Difference between using var and not using var in JavaScript](http://stackoverflow.com/questions/1470488/difference-between-using-var-and-not-using-var-in-javascript) – Bergi Jul 04 '13 at 11:00