Following your clarification this seems to be OK, though I would agree that a cleaner solution would be nice:
Options[f] = {foo -> bar, ImageSize -> 333}; f[args__, opts : OptionsPattern[{f, Button, Tooltip}]] := Append[ FilterRules[{opts, Options @ f}, Options @ #] & /@ {Button, Tooltip}, OptionValue[foo] ] // Column Test:
f[1, 2, Background -> Blue, AutoAction -> False, TooltipDelay -> 1] {Background -> RGBColor[0, 0, 1], AutoAction -> False, ImageSize -> 333} {Background -> RGBColor[0, 0, 1], TooltipDelay -> 1} bar
Following your updated requirements the only streamlining I can think to recommend is to combine the functionality of your mergeRules with that of FilterRules. This is a trivial refactoring but again I hope you find some value in the idea.
getRules[base_Symbol, op___][target_Symbol] := First /@ GatherBy[{op, Options @ base} ~FilterRules~ Options[target], First] Options[f] = {foo -> bar, ImageSize -> 333}; f[args__, opts : OptionsPattern[{f, Button, Tooltip}]] := Append[getRules[f, opts] /@ {Button, Tooltip}, OptionValue[foo]] // Column test:
f[1, 2, Background -> Blue, AutoAction -> False, TooltipDelay -> 1, ImageSize -> 99] {Background -> RGBColor[0, 0, 1], AutoAction -> False, ImageSize -> 99} {Background -> RGBColor[0, 0, 1], TooltipDelay -> 1} bar
getRules could also be written with KeyTake
getRules[base_Symbol, op___][target_Symbol] := {op, Options @ base} // Flatten // KeyTake[Keys @ Options @ target] // Normal