Pull to refresh

Красивые disabled иконки «малой кровью»

Reading time 4 min
Views 13K
Delphi давно славится тем, что disabled иконки по умолчанию выглядят как-то так:



А хотелось бы, чтоб они выглядели вот как-то так:



Воспользуемся тем, что 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, тогда иконки будут просто серые.
Tags:
Hubs:
+25
Comments 5
Comments Comments 5

Articles