1

EDIT: Only occurs in Firefox! (I'm using 22.0) See the browser comparison at the bottom.

I'm trying to create a 'fade to black' effect on the canvas by copying the pixel data and progressively changing the alpha values from 255 to 0 (the background is black).

function fadeToBlack () {
    if(typeof this.recursion === 'undefined' || this.recursion === 0) {
        this.recursion = 1;
        this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
        this.imageDataArray = this.imageData.data;
        this.pixelCount = this.imageDataArray.length/4;
        this.fadeToBlack();
    }
    else if (this.recursion <= 15){
        console.time('Change alpha ' + this.recursion);
        for (var i = 0; i < this.pixelCount; i++){
            this.imageDataArray[i * 4 + 3] = 255 - 255 / 15 * this.recursion;
        }
        console.timeEnd('Change alpha ' + this.recursion);
        this.ctx.putImageData(this.imageData, 0, 0);
        this.recursion++;
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 50);
    }
    else {
        this.recursion = 0;
    }
};

I thought this would be really expensive (1280 * 1024 = 1310720 iterations!), but as you can see from the console log below, it was surprisingly quick except for the first iteration.

Change alpha 1: 543ms
Change alpha 2: 16ms
Change alpha 3: 6ms
Change alpha 4: 16ms
...

Curiously, if I simply delay the second iteration of fadeToBlack (first iteration of the pixel manipulation)...

function fadeToBlack () {
    if(typeof this.recursion === 'undefined' || this.recursion === 0) {
        this.recursion = 1;
        this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
        this.imageDataArray = this.imageData.data;
        this.pixelCount = this.imageDataArray.length/4;
        //This is the only difference!
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 0);
    }
    else if (this.recursion <= 15){
        console.time('Change alpha ' + this.recursion);
        for (var i = 0; i < this.pixelCount; i++){
            this.imageDataArray[i * 4 + 3] = 255 - 255 / 15 * this.recursion;
        }
        console.timeEnd('Change alpha ' + this.recursion);
        this.ctx.putImageData(this.imageData, 0, 0);
        this.recursion++;
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 50);
    }
    else {
        this.recursion = 0;
    }
};

Something magical happens.

Change alpha 1: 16ms
Change alpha 2: 16ms
Change alpha 3: 6ms
Change alpha 4: 6ms
...

So what the heck is going on here?

EDIT: I tested this in several browsers and here are the results in milliseconds for all 15 iterations.

Browser  |Recursive  |Asynchronous
=========+===========+============
Firefox  |1652†      |1136
Chrome   |976        |978
Opera    |12929      |13855
IE       |855        |854

†First iteration was very expensive (500ms).

LittleJohn
  • 183
  • 2
  • 10
  • that is strange, but if you use setTimeout you are making the function execute as asynchronous and the current function will continue working while putting the next call in the queue to run in the background afterwards. that is not recursion anymore, as you don't depend on the value that it returns. I'm interested to know the reason of that boost, is it that when you call an async call and finish running the current call that removes the current from the memory and consider it as not related to the queue !? – CME64 Jul 01 '13 at 04:29
  • I was a bit fuzzy on asynchronous/recursive when I asked the question but now that I've read up on it a bit... I still have no idea where the performance boost comes from. I guess it's probably something very engine specific to do with 2 functions having access to the same 5mB set of data (this.imageData) at the same time. I might do some tests in the different browsers and see what happens. – LittleJohn Jul 01 '13 at 09:51
  • @CME64 Done! Definitely looks like an engine quirk! – LittleJohn Jul 01 '13 at 10:32

1 Answers1

1

I think that reduces the jumps between the functions by half, as you only call once till the function dies (when using async call using setTimeout), however if you use recursion by calling from within, it will stop at that point and jump to the next call and so on until it finishes the last call then go on recursion gradually to call the previous function from the line it stopped at to continue using the returned value from the recursion and return back to the previous one,, I can see a bid difference in performance and methodology. I've to ask if it gives you the same result which I suspect wouldn't be the case.

TL;DR setTimeout: asynchronous call (independent), recursion: synchronous (dependent).

graphical demonstration:

enter image description here

CME64
  • 1,673
  • 13
  • 24