Delphi has long been famous for the fact that the default disabled icons look something like this:

And I would like them to look like this:
')

We use the fact that Delphi allows you to replace the disabled icons with your own, specifying an additional list of images. But to draw and connect such icons every time is tedious. Therefore, we will create this list of images dynamically, during program execution.
We will create such a list of images in the special function CreateSpecialImageList (). As an argument, we will need a list with the original icons, and as the return value we will already need the TImageList we need. Then we can connect our new icons when creating a form with the following line of code:
ActionManager.DisabledImages := CreateSpecialImageList(ImageList);
But I think we should go deeper ...

Expand the capabilities of the function, add the ability to not only make gray translucent icons, but also make them brighter or more contrast. You may need this when creating icons for HotImages properties. Thus, we will have a universal function for creating icons of several special states.
Function source code:
function CreateSpecialImageList(ASource: TCustomImageList; ABrightness, AContrast, AGrayscale, AAlpha: Single): TCustomImageList; type PBGRA = ^TBGRA; TBGRA = packed record B, G, R, A: Byte; end; TByteLUT = array [Byte] of Byte; function ByteRound(const Value: Single): Byte; inline; begin if Value < 0 then Result := 0 else if Value > 255 then Result := 255 else Result := Round(Value); end; procedure GetLinearLUT(var LUT: TByteLUT; X1, Y1, X2, Y2: Integer); var X, DX, DY: Integer; begin DX := X2 - X1; DY := Y2 - Y1; for X := 0 to 255 do LUT[X] := ByteRound((X - X1) * DY / DX + Y1); end; function GetBrightnessContrastLUT(var LUT: TByteLUT; const Brightness, Contrast: Single): Boolean; var B, C: Integer; X1, Y1, X2, Y2: Integer; begin X1 := 0; Y1 := 0; X2 := 255; Y2 := 255; B := Round(Brightness * 255); C := Round(Contrast * 127); if C >= 0 then begin Inc(X1, C); Dec(X2, C); Dec(X1, B); Dec(X2, B); end else begin Dec(Y1, C); Inc(Y2, C); Inc(Y1, B); Inc(Y2, B); end; GetLinearLUT(LUT, X1, Y1, X2, Y2); Result := (B <> 0) or (C <> 0); end; var LImageInfo: TImageInfo; LDibInfo: TDibSection; I, L: Integer; P: PBGRA; R, G, B, A: Byte; LUT: TByteLUT; LHasLUT: Boolean; begin Result := nil; if (ASource = nil) or (ASource.ColorDepth <> cd32bit) then Exit; Result := TImageList.Create(ASource.Owner); try Result.ColorDepth := cd32bit; Result.Assign(ASource); Result.Masked := False; FillChar(LImageInfo, SizeOf(LImageInfo), 0); ImageList_GetImageInfo(Result.Handle, 0, LImageInfo); FillChar(LDibInfo, SizeOf(LDibInfo), 0); GetObject(LImageInfo.hbmImage, SizeOf(LDibInfo), @LDibInfo); P := LDibInfo.dsBm.bmBits; LHasLUT := GetBrightnessContrastLUT(LUT, ABrightness, AContrast); for I := 0 to LDibInfo.dsBm.bmHeight * LDibInfo.dsBm.bmWidth - 1 do begin A := PA; R := MulDiv(PR, $FF, A); G := MulDiv(PG, $FF, A); B := MulDiv(PB, $FF, A); if LHasLUT then begin R := LUT[R]; G := LUT[G]; B := LUT[B]; end; if AGrayscale > 0 then begin L := (R * 61 + G * 174 + B * 21) shr 8; if AGrayscale >= 1 then begin R := L; G := L; B := L; end else begin R := ByteRound(R + (L - R) * AGrayscale); G := ByteRound(G + (L - G) * AGrayscale); B := ByteRound(B + (L - B) * AGrayscale); end; end; if AAlpha <> 1 then begin A := ByteRound(A * AAlpha); PA := A; end; PR := MulDiv(R, A, $FF); PG := MulDiv(G, A, $FF); PB := MulDiv(B, A, $FF); Inc(P); end; except FreeAndNil(Result); end; end;
Function Arguments:
- ASource - source list of images
- Abrightness - brightness, from -1 to 1, the recommended range from -0.5 to 0.5
- AContrast - contrast, from -1 to 1, the recommended range is from -0.5 to 0.5
- AGrayscale - grayscale, from 0 - source color to 1 - completely gray
- AAlpha - transparency, from 0 - fully transparent to 1 - initial transparency
I think the source code of the function will be understandable to most delphists, but I consider it necessary to clarify a few points.
First, the DIB image in TImageList is stored in the premulted format and for each pixel we have to perform the operation of obtaining the original color channel value with a series of calls to MulDiv (), perform operations, and then return the original format of the pixel back.
Changing the brightness and contrast is done using the LUT (Lookup Table) lookup table. We build a table based on a linear function of 2 points. The brightness and contrast parameters simply shift the original points of the direct correction in the desired directions. Since we perform the same correction on all color channels, we use only one LUT.
Changing the grayscale for a pixel is done by simply calculating the offset from the original value to the value of the overall color brightness. The larger the value of the AGrayscale argument, the closer the new value is to the total brightness value, which, in aggregate, for all color channels gives an approximation to gray.
Use function
To use the function, you can simply copy the source code into your project. The function code is self-sufficient and does not require third-party components or modules. For convenience, I wrote a small demo in which you can experiment with function arguments. The source code of the demo can be downloaded
here .
Original icons

Original icons in standard disabled state

Icons with contrast increased by 10%

The icons in the disabled state are completely gray and with the transparency reduced by 50%.

I hope the function provided here will be useful to you in your developments. Enjoy your coding!
UPD : This method only works for image lists in ColorDepth = cd32bit mode
UPD2:: When running a compiled demo project, there may be a black background problem around the icons,
download the new version of the project . The problem arose due to incorrect transfer of the project from Delphi XE3 to Delphi XE.
UPD3 : When running on Windows 2000 for 32bit disabled icons, a blackout effect is obtained. To solve this problem, you can determine the OS and for the 2000th leave AAlpha = 1, then the icons will be just gray.