Delphi давно славится тем, что disabled иконки по умолчанию выглядят как-то так:
А хотелось бы, чтоб они выглядели вот как-то так:
Воспользуемся тем, что Delphi позволяет заменить disabled иконки своими, указав дополнительный список изображений. Но рисовать и подключать такие иконки каждый раз занятие утомительное. Поэтому мы создадим этот список изображений динамически, во время выполнения программы.
Создавать такой список изображений мы будем в специальной функции CreateSpecialImageList(). В качестве аргумента нам понадобится список с оригинальными иконками, а в качестве возвращаемого значения уже будет нужный нам TImageList. Тогда подключить наши новые иконки мы сможем при создании формы следующей строчкой кода:
Расширим возможности функции, добавим возможность делать не только серые полупрозрачные иконки, но и делать их ярче или контрастнее. Это может понадобиться при создании иконок для свойств HotImages. Таким образом, у нас будет универсальная функция для создания иконок нескольких специальных состояний.
Я думаю, исходный код функции большинству дельфистов будет понятен, но считаю нужным пояснить несколько моментов.
Во первых, DIB изображение в TImageList хранится в premulted формате и для каждого пиксела мы должны выполнить операцию получения исходного значения цветового канала серией вызовов MulDiv(), выполнить операции, а затем вернуть исходный формат пиксела обратно.
Изменение яркости и контраста выполняется с использованием LUT (Lookup Table) — таблицы поиска. Мы строим таблицу на основе линейной функции по 2-м точкам. Параметры яркости и контраста просто сдвигают исходные точки прямой коррекции по нужным направлениям. Так как мы выполняем одинаковую коррекцию по всем цветовым каналам, то используем только одну LUT.
Изменение градаций серого для пиксела выполняется простым вычислением смещения от исходного значения к значению общей яркости цвета. Чем больше значение аргумента AGrayscale, тем ближе новое значение к значению общей яркости, что в совокупности для всех цветовых каналов дает приближение к серому цвету.
Для использования функции вы можете просто скопировать исходный код в свой проект. Код функции самодостаточен и не требует сторонних компонентов или модулей. Для удобства я написал небольшую демку, в которой вы можете поэкспериментировать с аргументами функции. Исходный код демки можно скачать тут.
Надеюсь, приведенная здесь функция пригодится вам в ваших разработках. Приятного кодинга!
UPD: Данный метод работает только для списков изображений в режиме ColorDepth = cd32bit
UPD2:: При запуске скомпилированного демо-проекта может возникнуть проблема «черного» фона вокруг иконок, скачайте новую версию проекта. Проблема возникла вследствие некорректного переноса проекта из Delphi XE3 в Delphi XE.
UPD3: При запуске на Windows 2000 для 32bit-ных disabled иконок получается эффект затемнения. Для решения данной проблемы можно определять OS и для 2000-й оставлять AAlpha = 1, тогда иконки будут просто серые.
А хотелось бы, чтоб они выглядели вот как-то так:
Воспользуемся тем, что Delphi позволяет заменить disabled иконки своими, указав дополнительный список изображений. Но рисовать и подключать такие иконки каждый раз занятие утомительное. Поэтому мы создадим этот список изображений динамически, во время выполнения программы.
Создавать такой список изображений мы будем в специальной функции CreateSpecialImageList(). В качестве аргумента нам понадобится список с оригинальными иконками, а в качестве возвращаемого значения уже будет нужный нам TImageList. Тогда подключить наши новые иконки мы сможем при создании формы следующей строчкой кода:
ActionManager.DisabledImages := CreateSpecialImageList(ImageList);
Но я думаю, мы должны пойти глубже...
Расширим возможности функции, добавим возможность делать не только серые полупрозрачные иконки, но и делать их ярче или контрастнее. Это может понадобиться при создании иконок для свойств HotImages. Таким образом, у нас будет универсальная функция для создания иконок нескольких специальных состояний.
Исходный код функции:
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 := P.A;
R := MulDiv(P.R, $FF, A);
G := MulDiv(P.G, $FF, A);
B := MulDiv(P.B, $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);
P.A := A;
end;
P.R := MulDiv(R, A, $FF);
P.G := MulDiv(G, A, $FF);
P.B := MulDiv(B, A, $FF);
Inc(P);
end;
except
FreeAndNil(Result);
end;
end;
Аргументы функции:
- ASource — исходный список изображений
- ABrightness — яркость, от -1 до 1, рекомендуемый диапазон от -0.5 до 0.5
- AContrast — контраст, от -1 до 1, рекомендуемый диапазон от -0.5 до 0.5
- AGrayscale — градации серого, от 0 — исходный цвет до 1 — полностью серый
- AAlpha — прозрачность, от 0 — полностью прозрачный до 1 — исходная прозрачность
Я думаю, исходный код функции большинству дельфистов будет понятен, но считаю нужным пояснить несколько моментов.
Во первых, DIB изображение в TImageList хранится в premulted формате и для каждого пиксела мы должны выполнить операцию получения исходного значения цветового канала серией вызовов MulDiv(), выполнить операции, а затем вернуть исходный формат пиксела обратно.
Изменение яркости и контраста выполняется с использованием LUT (Lookup Table) — таблицы поиска. Мы строим таблицу на основе линейной функции по 2-м точкам. Параметры яркости и контраста просто сдвигают исходные точки прямой коррекции по нужным направлениям. Так как мы выполняем одинаковую коррекцию по всем цветовым каналам, то используем только одну LUT.
Изменение градаций серого для пиксела выполняется простым вычислением смещения от исходного значения к значению общей яркости цвета. Чем больше значение аргумента AGrayscale, тем ближе новое значение к значению общей яркости, что в совокупности для всех цветовых каналов дает приближение к серому цвету.
Использование функции
Для использования функции вы можете просто скопировать исходный код в свой проект. Код функции самодостаточен и не требует сторонних компонентов или модулей. Для удобства я написал небольшую демку, в которой вы можете поэкспериментировать с аргументами функции. Исходный код демки можно скачать тут.
Оригинальные иконки
Оригинальные иконки в стандартном disabled состоянии
Иконки с контрастом увеличенным на 10%
Иконки в disabled состоянии полностью серые и с прозрачностью уменьшенной на 50%
Надеюсь, приведенная здесь функция пригодится вам в ваших разработках. Приятного кодинга!
UPD: Данный метод работает только для списков изображений в режиме ColorDepth = cd32bit
UPD2:: При запуске скомпилированного демо-проекта может возникнуть проблема «черного» фона вокруг иконок, скачайте новую версию проекта. Проблема возникла вследствие некорректного переноса проекта из Delphi XE3 в Delphi XE.
UPD3: При запуске на Windows 2000 для 32bit-ных disabled иконок получается эффект затемнения. Для решения данной проблемы можно определять OS и для 2000-й оставлять AAlpha = 1, тогда иконки будут просто серые.