anchor.diff:      begin
         //Filenames are 1-based, and skip the first one since it's the original
         suffixPath := 'interpolated_frames\' + format('%.15d.png', [SubStep+1+1]);
  -
  -      //Clear all our buffers so they get rezeroed
  -      SetLength(pngBase, 0);
  -      SetLength(pngShad, 0);
  -      SetLength(pngTeam, 0);
  -      SetLength(pngCrop, 0);
  -      SetLength(pngCropMask, 0);
  -
  -      LoadFromPng(dirBase + suffixPath, pngWidth, pngHeight, pngBase);
  -
  -      if FileExists(dirShad + suffixPath) then
  -        LoadFromPng(dirShad + suffixPath, pngWidth, pngHeight, pngShad);
  -
  -      if FileExists(dirTeam + suffixPath) then
  -        LoadFromPng(dirTeam + suffixPath, pngWidth, pngHeight, pngTeam);
  -
  -      //Determine Min/Max X/Y
  -      MinX := MaxInt;
  -      MinY := MaxInt;
  -      MaxX := -1;
  -      MaxY := -1;
  -      NoShadMinX := pngWidth-1;
  -      NoShadMinY := pngHeight-1;
  -      NoShadMaxX := 0;
  -      NoShadMaxY := 0;
  -      needsMask := False;
  -      for Y := 0 to pngHeight-1 do
  -      begin
  -        for X := 0 to pngWidth-1 do
  -        begin
  -          if (pngBase[Y*pngWidth + X] shr 24 > 50) or
  -          ((Length(pngShad) > 0) and ((pngShad[Y*pngWidth + X] and $FF) > 10)) then
  -          begin
  -            MinX := Min(MinX, X);
  -            MinY := Min(MinY, Y);
  -            MaxX := Max(MaxX, X);
  -            MaxY := Max(MaxY, Y);
  -          end;
  -          //Tighter "no shadow" bounds. Also ignore areas where alpha < 50%
  -          if pngBase[Y*pngWidth + X] shr 24 >= $7F then
  -          begin
  -            NoShadMinX := Min(NoShadMinX, X);
  -            NoShadMinY := Min(NoShadMinY, Y);
  -            NoShadMaxX := Max(NoShadMaxX, X);
  -            NoShadMaxY := Max(NoShadMaxY, Y);
  -          end;
  -          if (Length(pngTeam) > 0) and ((pngTeam[Y*pngWidth + X] and $FF) > 0) then
  -            needsMask := True;
  -        end;
  -      end;
  -
  -      //Apply overall bounds to crop out the base background sprite
  -      MinX := Max(MinX, OverallMinX + (pngWidth div 2));
  -      MinY := Max(MinY, OverallMinY + (pngHeight div 2) + CANVAS_Y_OFFSET);
  -      MaxX := Min(MaxX, OverallMaxX + (pngWidth div 2));
  -      MaxY := Min(MaxY, OverallMaxY + (pngHeight div 2) + CANVAS_Y_OFFSET);
  -
  -      //Crop
  -      if (MaxX > MinX) and (MaxY > MinY) then
  -      begin
  -        newWidth := MaxX - MinX + 1;
  -        newHeight := MaxY - MinY + 1;
  -        SetLength(pngCrop, newWidth*newHeight);
  -        SetLength(pngCropMask, newWidth*newHeight);
  -        I := 0;
  -        for Y := MinY to MaxY do
  -        begin
  -          for X := MinX to MaxX do
  -          begin
  -            //Background is black with alpha from the shadow mask
  -            if Length(pngShad) > 0 then
  -              pngCrop[I] := (pngShad[Y*pngWidth + X] shl 24) or aBkgRGB;
  -
  -            //Layer base sprite on top
  -            pngCrop[I] := BlendRGBA(pngCrop[I], pngBase[Y*pngWidth + X]);
  -
  -            if Length(pngTeam) > 0 then
  -              pngCropMask[I] := pngTeam[Y*pngWidth + X];
  -
  -            Inc(I);
  -          end;
  -        end;
  -
  -        //Save offsets .txt file
  -        StrList.Clear;
  -        StrList.Append(IntToStr(MinX - pngWidth div 2));
  -        StrList.Append(IntToStr(MinY - CANVAS_Y_OFFSET - pngHeight div 2));
  -        StrList.Append(IntToStr(NoShadMinX - MinX));
  -        StrList.Append(IntToStr(NoShadMinY - MinY));
  -        StrList.Append(IntToStr(newWidth-1 - (MaxX - NoShadMaxX)));
  -        StrList.Append(IntToStr(newHeight-1 - (MaxY - NoShadMaxY)));
  -
  -        StrList.SaveToFile(outDirLocal+format('%d_%d.txt', [Byte(RT)+1, InterpOffset+SubStep]));
  -        SaveToPng(newWidth, newHeight, pngCrop, outDirLocal+format('%d_%d.png', [Byte(RT)+1, InterpOffset+SubStep]));
  -        if needsMask and (Length(pngTeam) > 0) then
  -          SaveToPng(newWidth, newHeight, pngCropMask, outDirLocal+format('%d_%dm.png', [Byte(RT)+1, InterpOffset+SubStep]));
  -      end;
  +      ProcessInterpImage(InterpOffset+SubStep, suffixPath, outPrefix, aBkgRGB, OverallMaxX, OverallMinX, OverallMaxY, OverallMinY);
       end;
     end;
   
     FreeAndNil(StrList);
   end;
  +
  +
  +procedure TForm1.ProcessInterpImage(outIndex: Integer; inSuffixPath, outPrefixPath: string; aBkgRGB: Cardinal; OverallMaxX, OverallMinX, OverallMaxY, OverallMinY: Integer);
  +var
  +  pngWidth, pngHeight, newWidth, newHeight: Word;
  +  pngBase, pngShad, pngTeam, pngCrop, pngCropMask: TKMCardinalArray;
  +  I, X, Y, MinX, MinY, MaxX, MaxY: Integer;
  +  NoShadMinX, NoShadMinY, NoShadMaxX, NoShadMaxY: Integer;
  +  needsMask: Boolean;
  +  StrList: TStringList;
  +  dirBase, dirShad, dirTeam: string;
  +begin
  +  dirBase := fWorkDir + 'base\';
  +  dirShad := fWorkDir + 'shad\';
  +  dirTeam := fWorkDir + 'team\';
  +
  +  //Clear all our buffers so they get rezeroed
  +  SetLength(pngBase, 0);
  +  SetLength(pngShad, 0);
  +  SetLength(pngTeam, 0);
  +  SetLength(pngCrop, 0);
  +  SetLength(pngCropMask, 0);
  +
  +  LoadFromPng(dirBase + inSuffixPath, pngWidth, pngHeight, pngBase);
  +
  +  if FileExists(dirShad + inSuffixPath) then
  +    LoadFromPng(dirShad + inSuffixPath, pngWidth, pngHeight, pngShad);
  +
  +  if FileExists(dirTeam + inSuffixPath) then
  +    LoadFromPng(dirTeam + inSuffixPath, pngWidth, pngHeight, pngTeam);
  +
  +  //Determine Min/Max X/Y
  +  MinX := MaxInt;
  +  MinY := MaxInt;
  +  MaxX := -1;
  +  MaxY := -1;
  +  NoShadMinX := pngWidth-1;
  +  NoShadMinY := pngHeight-1;
  +  NoShadMaxX := 0;
  +  NoShadMaxY := 0;
  +  needsMask := False;
  +  for Y := 0 to pngHeight-1 do
  +  begin
  +    for X := 0 to pngWidth-1 do
  +    begin
  +      if (pngBase[Y*pngWidth + X] shr 24 > 50) or
  +      ((Length(pngShad) > 0) and ((pngShad[Y*pngWidth + X] and $FF) > 10)) then
  +      begin
  +        MinX := Min(MinX, X);
  +        MinY := Min(MinY, Y);
  +        MaxX := Max(MaxX, X);
  +        MaxY := Max(MaxY, Y);
  +      end;
  +      //Tighter "no shadow" bounds. Also ignore areas where alpha < 50%
  +      if pngBase[Y*pngWidth + X] shr 24 >= $7F then
  +      begin
  +        NoShadMinX := Min(NoShadMinX, X);
  +        NoShadMinY := Min(NoShadMinY, Y);
  +        NoShadMaxX := Max(NoShadMaxX, X);
  +        NoShadMaxY := Max(NoShadMaxY, Y);
  +      end;
  +      if (Length(pngTeam) > 0) and ((pngTeam[Y*pngWidth + X] and $FF) > 0) then
  +        needsMask := True;
  +    end;
  +  end;
  +
  +  //Apply overall bounds to crop out the base background sprite
  +  MinX := Max(MinX, OverallMinX + (pngWidth div 2));
  +  MinY := Max(MinY, OverallMinY + (pngHeight div 2) + CANVAS_Y_OFFSET);
  +  MaxX := Min(MaxX, OverallMaxX + (pngWidth div 2));
  +  MaxY := Min(MaxY, OverallMaxY + (pngHeight div 2) + CANVAS_Y_OFFSET);
  +
  +  //Crop
  +  if (MaxX > MinX) and (MaxY > MinY) then
  +  begin
  +    newWidth := MaxX - MinX + 1;
  +    newHeight := MaxY - MinY + 1;
  +    SetLength(pngCrop, newWidth*newHeight);
  +    SetLength(pngCropMask, newWidth*newHeight);
  +    I := 0;
  +    for Y := MinY to MaxY do
  +    begin
  +      for X := MinX to MaxX do
  +      begin
  +        //Background is black with alpha from the shadow mask
  +        if Length(pngShad) > 0 then
  +          pngCrop[I] := (pngShad[Y*pngWidth + X] shl 24) or aBkgRGB;
  +
  +        //Layer base sprite on top
  +        pngCrop[I] := BlendRGBA(pngCrop[I], pngBase[Y*pngWidth + X]);
  +
  +        if Length(pngTeam) > 0 then
  +          pngCropMask[I] := pngTeam[Y*pngWidth + X];
  +
  +        Inc(I);
  +      end;
  +    end;
  +
  +    //Save offsets .txt file
  +    StrList := TStringList.Create;
  +    StrList.Append(IntToStr(MinX - pngWidth div 2));
  +    StrList.Append(IntToStr(MinY - CANVAS_Y_OFFSET - pngHeight div 2));
  +    StrList.Append(IntToStr(NoShadMinX - MinX));
  +    StrList.Append(IntToStr(NoShadMinY - MinY));
  +    StrList.Append(IntToStr(newWidth-1 - (MaxX - NoShadMaxX)));
  +    StrList.Append(IntToStr(newHeight-1 - (MaxY - NoShadMaxY)));
  +
  +    StrList.SaveToFile(outPrefixPath+format('%d.txt', [outIndex]));
  +    SaveToPng(newWidth, newHeight, pngCrop, outPrefixPath+format('%d.png', [outIndex]));
  +    if needsMask and (Length(pngTeam) > 0) then
  +      SaveToPng(newWidth, newHeight, pngCropMask, outPrefixPath+format('%dm.png', [outIndex]));
  +
  +    FreeAndNil(StrList);
  +  end;
  +end;
  +
   
   
   procedure TForm1.DoInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; var aPicOffset: Integer; aDryRun: Boolean);
  
positive.code: 
  
  procedure TForm1.DoInterpTree(aTree: Integer; var aPicOffset: Integer; aDryRun: Boolean);
  var
    A: TKMAnimLoop;
  begin
    A := gMapElements[aTree].Anim;
  
  
negative.code:   //Keep 1px padding on all sides
    MaxSoFar := MaxSoFar + 2;
    Result := ((MaxSoFar div 32) + 1)*32;
  end;
  
  
  procedure TForm1.Make4xInterpImages(RT: TRXType; aID_1, aID_2: Integer; aBaseDir: string; aExportType: TInterpExportType);
  var
    origSpritesDir, interpSpritesDir: string;
    I: Integer;
    NeedAlpha: Boolean;
    StartupInfo: TStartupInfo;
    ProcessInfo: TProcessInformation;
  begin
    MakeInterpImagesPair(RT, aID_1, aID_2, -1, -1, 0, 0, False, aBaseDir, aExportType);
  
    origSpritesDir := aBaseDir + 'original_frames\';
    interpSpritesDir := aBaseDir + 'interpolated_frames\';
  
    KMCopyFile(origSpritesDir + format('%d.png', [2]), interpSpritesDir + format('%.15d.png', [9]));
    KMDeleteFolderContent(origSpritesDir);
  
    for I := 1 to 9 do
      KMCopyFile(interpSpritesDir + format('%.15d.png', [I]), origSpritesDir + format('%d.png', [I]));
  
    KMDeleteFolderContent(interpSpritesDir);
  
    NeedAlpha := aExportType in [ietBase, ietNormal];
  
    //Interpolate
    ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
    StartupInfo.cb := SizeOf(StartupInfo);
    StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
    StartupInfo.wShowWindow := SW_HIDE;
    CreateProcess(nil, PChar(GetDainParams(aBaseDir, NeedAlpha, 4)), nil, nil, false, 0, nil, PChar(fDainFolder), StartupInfo, ProcessInfo);
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    //ShellExecute(0, nil, 'cmd.exe', PChar(DainParams), PChar(DainFolder), SW_SHOWNORMAL);
  end;
  
  
  