Tag Archive for 'Pixel Bender'

How I made the “TV glitch effect” for va.lent.in

You should have seen the effect of a TV signal glitch on text at my last va.lent.in site. People keep asking me how it was done over and over again. I won’t post complete source code here because it’s kind of messy. I’ll just give basic ideas, because this effect is rather simple.

Basically, you just displace red, green and blue channels of a bitmap separately and apply the same actions to resulting bitmap. Adding some kind of wave function looks cool too, but is damn slow. So, what I decided to do is to distort a small rectangle of target image over a couple of frames. First, I used only BitmapDatas but later tried to test PixelBender performance. Honestly, I don’t remember any miraculous speed gain, so there might be none at all. But anyway, here’s the code of this filter. Basically, it’s just 3 lines of code:

void evaluatePixel()
{
    float2 outCoords = outCoord();
    dst.r = sample( src, float2(outCoords.x + rOffset, outCoords.y) ).r;
    dst.g = sample( src, float2(outCoords.x + gOffset, outCoords.y) ).g;
    dst.b = sample( src, float2(outCoords.x + bOffset, outCoords.y) ).b;
}

Full source code you can download here: RGBDisplacement.pbk

This shader takes 3 displacement values as parameters. Now back to AS3. Once again, this code is result of numerous experiments, changes and fixes during development. Please don’t use it as is (actually it doesn’t work alone) but rather try to understand the basic idea.

[Embed(source="filters/RGBDisplacement.pbj", mimeType="application/octet-stream")]
 private var ShaderDistort:Class;

 public function Distortion( source: Sprite, minAmp: int = 10, maxAmp: int = 40, len: int = 30, minRand: Number = .05, ampRand: Number = .8, maxH: int = 20 )
 { ... }

Shader class and constructor params.

public function start( interval: int = -1 ): void
{
    if ( bitmapData.width != Math.ceil(_source.width) || bitmapData.height != Math.ceil(_source.height) )
    {
        initBitmaps();
    }

    seed = 0;
    _source.alpha = 0;
    visible = true;
    this.interval = interval;
    // set random displacement
    bmpY = Math.random()*(bitmapData.height-bitmapDataDistorted.height);

    bitmapData.fillRect( bitmapData.rect, 0x000000 );
    bitmapData.draw( _source );
    business();
 }

This method resets distortion params, sets bmpY to a random starting y position and invokes business() which does the job.

private function business( evt: Event = null ): void
{
    // stop if played for certain number of frames
    if (interval == 0)
    {
        stop();
        return;
    }
    interval--;

    // randomly move the effect up or down
    bmpY += int((10 - Math.random()*20) * Math.sqrt(interval));
    bmpY < 0 ? bmpY = 0 : true;
    bmpY > bitmapData.height-bitmapDataDistorted.height ? bmpY = bitmapData.height-bitmapDataDistorted.height : true;

    var bmpd: BitmapData = new BitmapData( bitmapDataDistorted.width, bitmapDataDistorted.height, false, 0x000000 );

    bmpd.copyPixels( bitmapData, new Rectangle(0, bmpY, bitmapDataDistorted.width, bitmapDataDistorted.height ), new Point(0, 0) );
    filter.data.src.input = bmpd;

    seed++;
    seed >= len ? seed = 0: true;

    // wave function
    var v: Number = 3.14159265 / len * seed;
    // approximation of sin()
    var sin: Number = 1.27323954 * v - 0.405284735 * v * v;
    var amp: Number = minAmp + sin * difAmp;

    var i: int = 0;
    var disp: Array = [];
    var pi2: Number = 6.28318531;

    for (i; i<3; i++)
    {
        if ( seeds[i] >= 1 )
        {
            seeds[i]--;
            dSeeds[i] = Math.random() * ampRand + minRand;
        }
        seeds[i] += dSeeds[i];
        v = seeds[i] * pi2;
        // approximation of sin()
        if (v > 3.14159265) v -= 6.28318531;
        if (v < 0)
            sin = 1.27323954 * v + .405284735 * v * v;
        else
            sin = 1.27323954 * v - 0.405284735 * v * v;
        disp[i] = sin * amp;
    }

    // set displacements for shader
    filter.data.rOffset.value = [disp[0]];
    filter.data.gOffset.value = [disp[1]];
    filter.data.bOffset.value = [disp[2]];

    // invoke shader
    var job: ShaderJob = new ShaderJob(filter, bitmapDataDistorted);
     job.addEventListener(ShaderEvent.COMPLETE, redraw);
    job.start();
}

And finally redraw() method

private function redraw( evt: Event = null ): void
{
     bitmapData.copyPixels( bitmapDataDistorted, bitmapDataDistorted.rect, new Point(0, bmpY) );
     id = setTimeout(business, 40);
}

That’s it. You can refer to my post about using Maple for more math.

The source of this class is here: Distortion.as