First:
Private Function ChangeColorBrightness(clrIn As Color, brightnessChange As Single) As Color
Dim snrgb(), i6Y, bb, br As Single
snrgb = {clrIn.R, clrIn.G, clrIn.B}
i6Y = 0.299 * snrgb(0) + 0.587 * snrgb(1) + 0.114 * snrgb(2)
bb = 0.492 * (snrgb(2) - i6Y)
br = 0.877 * (snrgb(0) - i6Y)
i6Y += brightnessChange
snrgb(0) = Math.Clamp(i6Y + 1.14 * br, 0, 255)
snrgb(1) = Math.Clamp(i6Y - 0.395 * bb - 0.581 * br, 0, 255)
snrgb(2) = Math.Clamp(i6Y + 2.033 * bb, 0, 255)
Return Color.FromArgb(clrIn.A, snrgb(0), snrgb(1), snrgb(2))
End Function
Naturally, a large enough absolute value for brightnessChange to force clamping will color-shift, with pulls toward white or black. This blog does not suggest this function is compliant with any standard.
Remarks:
All code is presented "as is," and is free to use without constraint.
In the program screencap pictured below, I use color-retaining brightness shifts when drawing balls. The balls pictured below have three radial-gradient stops: normal color, color with brightness plus 20, and color with brightness plus 50.
The function I've listed above will: 1) change the color description from RGB a different "color space" that has luminance as a value; 2) change that luminance value by a parameter; 3) recombine the RGB values from the other color space and return that.