Tutorial step 4: adding start and end routines

Let's say we want to change our processing formula, so that instead of dividing green and blue in half, we divide it by 3 instead. This immediately gets gross. Division is veeeeeeerry slow, and multiplication tricks aren't much faster and lead to all sorts of rounding problems. Instead, we'll use lookup tables. Now, the only question is where to allocate and initialize the tables.

The startProc and endProc functions

The startProc function is called immediately before processing starts, and endProc after processing ends. These are the ideal routines to allocate and initialize whatever extra work buffers, lookup tables, or dynamic code strings that may be needed. These functions have the usual prototypes:

Function startProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
Function endProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;

As usual, return zero for success, non-zero for failure. Unlike the module init functions, VirtualDub calls the endProc function for all filters in the processing chain, including those that have not been initialized and the filter that failed. This means you must keep track of elements that were never allocated (*cou NULL gh*), but also means that you need not worry about deallocating anything in startProc on failure; you can simply net stopProc clean up the partial allocations.

For this to work, though, you must have instance data for the filter. This is done with a non-zero inst_data_size field in the FilterDefinition structure. VirtualDub will automatically allocate a memory block of that size and point the fa->filter_data member to it when a filter instance is created, and deallocate it when instances are destroyed. The memory block is also cleared with zeroes on creation. Because VirtualDub automatically handles instance data allocation, if your filter specifies it needs an instance data block the fa->filter_data pointer will always be valid whenever one of the filter functions is called.

You must not store pointers to locations in the instance data block. VirtualDub clones and destroys copies of the fa->filter_data structure when handling the Video Filter dialog. Pointers to the instance data block may not be valid after the dialog appears. This means that you should be especially careful with embedding classes directly in your filter structure. However, it is OK for the instance data block to point to other memory blocks.

Modifying the tutorial filter to use lookup tables

First step is to create a structure for our instance data. We'll use two 1K lookup tables, one for green and one for blue. With a Pixel32 for each entry, we can save ourselves some post-shifting trouble.

type
 PArrayPixel32 = ^arrayPixel32;
 ArrayPixel32 = array[0..255] of TPixel32;
 PFilterData = ^TFilterData;
 TFilterData = packed record
   grn_tab: PArrayPixel32;
   blu_tab: PArrayPixel32;
   end;

Next, update the FilterDefinition record:

Const
  filterDef_tutorial: TFilterDefinition = (
   next:           nil;
   prev:           nil;
   module:         nil;
   name:           'tutorial';
   desc:           'Emphasizes green and de-emphasizes blue in the image.';
   maker:          'anyone';
   private_data:   nil;
   inst_data_size: SizeOf(TFilterData);
   initProc:       nil;
   deinitProc:     nil;
   runProc:        tutorialRunProc;
   paramProc:      nil;
   configProc:     nil;
   stringProc:     nil;
   startProc:      tutorialStartProc;
   endProc:        tutorialEndProc;
   script_obj:     nil;
   fssProc:        nil);

Now, write the startProc and stopProc routines. These need not be particularly fast, so there's no sense in optimizing them much:

Function tutorialStartProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
 mfd: PFilterData;
 i: Integer;
begin {tutorialStartProc}
mfd := PFilterData(fa^.filter_data);
new(mfd^.grn_tab);
new(mfd^.blu_tab);
for i := 0 to 255 do begin
 mfd^.grn_tab[i] := (($200 + i) div 3) shl 8;
 mfd^.blu_tab[i] := i div 3;
 end;
Result := 0;
end; {tutorialStartProc}

Function tutorialEndProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
 mfd: PFilterData;
begin {tutorialEndProc}
mfd := PFilterData(fa^.filter_data);
if mfd^.grn_tab <> nil then dispose(mfd^.grn_tab);
if mfd^.blu_tab <> nil then dispose(mfd^.blu_tab);
Result := 0;
end; {tutorialEndProc}

Finally, rewrite the runProc routine to use lookup tables:

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

begin {tutorialRunProc}
src := PPixel32(fa^.src^.data);
dst := PPixel32(fa^.dst^.data);
mfd := PFilterData(fa^.filter_data);
h := fa^.src^.h;
repeat
  w := fa^.src^.w;
  repeat
    old_pixel := src^;
    new_pixel := (old_pixel AND $FF0000)
                 + mfd^.grn_tab[(old_pixel shr 8) AND $FF]
                 + mfd^.blu_tab[old_pixel AND $FF];
    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}

The operation was completed successfully. Your project file should now look like this. Compile and test it.