Tutorial step 1: creating the main routines

The main routine of any VirtualDub filter is the run function, which has this prototype:

function runProc(const fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;

The main items of interest are in the fa parameter:

  • fa^.src describes the source bitmap.
  • fa^.dst describes the destination bitmap.

Both structures are of type PCObj_VFBitmap. The main fields are:

  • data is a pointer to the bottom-left corner of the bitmap. All bitmaps are 32-bit RGB.
  • w, h describe the width and height of the bitmap.
  • pitch is the address offset, in bytes, between rows.
  • modulo is the address offset, in bytes, between the end of one row and the beginning of the next.
  • size is equal to pitch times h.

Creating a dummy function

Here is a skeletal runProc function that simply runs source and destination pointers over the bitmap:

Function tutorialRunProc(const fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
  w,h: TPixDim;
  src,dst: PPixel32;

begin {tutorialRunProc}
src := PPixel32(fa^.src^.data);
dst := PPixel32(fa^.dst^.data);
h := fa^.src^.h;
repeat
  w := fa^.src^.w;
  repeat
    Inc(src);
    Inc(dst);
    Dec(w);
  until w = 0;
  Inc(src, fa^.src^._modulo);
  Inc(dst, fa^.dst^._modulo);
  Dec(h);
until h = 0;
Result := 0;
end; {tutorialRunProc}

Note that because VirtualDub bitmaps are inverted like Windows DIBs, this routine scans from bottom to top. Scanlines are still left-to-right, though.

Implementing our color shift

We want to emphasize green, and deemphasize blue. The easiest way to do this is to halve both blue and green, and to add 50% to green. 32-bit RGB format defines 8-bits for each pixel in $RRGGBB format, so we'll use this as our pixel alteration formula:

new_pixel := (old_pixel AND $FF0000) + ((old_pixel AND $00FEFE) shr 1) + $008000;

This breaks down as follows:

  • (old_pixel AND $FF0000) extracts red only.
  • ((old_pixel AND $00FEFE) shr 1) extracts green and blue, and then divides them by two. The mask zeroes the LSB of green so it doesn't get shifted into the MSB of blue (blue's LSB doesn't matter).
  • $008000 brightens green, so the output ranges from 80 to FF.

Now we can write this into our inner loop:

Function tutorialRunProc(const fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
  w, h: TPixDim;
  src, dst: PPixel32;
  old_pixel, new_pixel: TPixel32;

begin {tutorialRunProc}
src := PPixel32(fa^.src^.data);
dst := PPixel32(fa^.dst^.data);
h := fa^.src^.h;
repeat
  w := fa^.src^.w;
  repeat
    old_pixel := src^;
    new_pixel := (old_pixel AND $FF0000) + ((old_pixel AND $00FEFE) shr 1) + $008000;
    dst^ := new_pixel;
    Inc(src);
    Inc(dst);
    Dec(w);
  until w = 0;
  Inc(src, fa^.src^._modulo);
  Inc(dst, fa^.dst^._modulo);
  Dec(h);
until h = 0;
Result := 0;
end; {tutorialRunProc}

That's it! We're done with the main processing routine, for now.