скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

O сохранении иконок 32х32 в 256-цветном формате



Автор: Владимир

Суть вопроса: я столкнулся с проблемой сохранения полноцветных иконок, когда понадобилось немного изменить имеющиеся у меня для своих программ. Ни родной Image Editor от Delphi6, ни другие редакторы не смогли мне помочь. Могли это делать платные редакторы, но они не для нас. Начав разбираться, я обнаружил, что созданная функцией CreateIconIndirect иконка нормально выглядит, если после создания ее кинуть на форму, однако после записи Icon.SaveToFile иконка обезображивается. Это происходит на стадии записи иконки. Поискав информацию, облазив форумы, я понял, что либо этой проблемой никто не занимался, либо с ней все мирятся и заниматься не хотят, хотя интересующиеся имеются.

Став заниматься проблемой вплотную, я решил использовать нетипизированные файлы, которым все равно, что в них записывают. Эти файлы хорошо подходят для работы с массивами байт и, если создать массив полностью соответствующий структуре иконки, то записав массив в нетипизированный файл, получим искомую иконку.

Пришлось изучить структуру файла ICO и вот результат.

Для использования процедуры нет необходимости изучать файл ICO, если только вы не хотите ничего изменять, например, размеры иконки. В принципе, можно сделать универсальную процедуру, которая бы определяла размер иконки и, в соответствии с размерами, создавала иконку. Все адреса и размеры, указанные в процедуре, являются абсолютными и изменению не подлежат, так как именно от них зависит, будет ли соответствовать создаваемый массив файлу ICO. В процедуру введено автоопределение количества цветов палитры, от этого зависит структура файла. Вам нет нужды указывать количество используемых цветов.

Кроме того, введено определение размера рисунка и если он не 32х32, то процедура прекратит работу. Это произойдет и в случае если кому-то захочется использовать более 256 цветов. Тем, кто хочет узнать о структуре файлов ICO, читать после кода процедуры. Используя этот метод можно работать с любыми файлами, например, для открытия и побайтного изменения EXE-файлов. Для работы этой процедуры нужен рисунок 32х32 на канве (у меня Image2), диалоговое окно SaveDialog1 (но не SavePictureDialog1) и все. Если нужно, измените имя канвы, оно встречается в двух строчках кода, и имя диалогового окна. Процедура с комментариями ниже:

//Записываем картинку как иконку (файл .ico)
{Так как стандартные функции не дают возможности создавать 256 цветные
иконки, мы пойдем другим путем, хоть он и медленнее и длиннее.
Создадим массив байт, который запишем как файл, это и будет
искомая иконка. Для этого создадим промежуточные массивы, иначе процедура
станет слишком навороченной, затем проверим количество используемых цветов
и, в зависимости от этого, создадим окончательный массив.}

procedure TForm1.ToolButton6Click(Sender: TObject);
var
  MAnd, MXor, MOsn, MCol: array of Byte;
  MPix, MOrg: array of TColor;
  a, b, c, n, m: integer;
  Bt, Bu, Indicator: Byte;
  p: TColor;
  k: boolean;
  F: file;
  LenOsn, LenXor, LenCol: integer;
begin
  if (Image2.Picture.Height <> 32) or (Image2.Picture.Width <> 32) then
  begin
    ShowMessage('Исходный рисунок не 32х32');
    exit;
  end;
  {Создание массивов: MAnd - массив маски AND, MXor - массив маски Xor,
  основного массива MOsn - куда будет собираться вся информация,
  массив MPix - массив пикселов рисунка начиная с левого нижнего угла,
  MCol - массив таблицы цветов - палитры, MOrg - массив оригинальных цветов}
  {Заполнение массивов MPix и MAnd}
  {Активизация массивов}
  SetLength(MPix, 1024);
  SetLength(MAnd, 128);
  {Установка счетчиков}
  b := 0; //Счетчик битов
  a := 0; //Счетчик пикселов картинки
  c := 0; //Счетчик байтов маски MAnd
  Bt := 0; //Формируемый байт маски MAnd
  {Преобразование картинки в удобный формат, с началом от 0,
  и перенос ее в таком виде в массив MPix, меняя по пути белый -
  будущий прозрачный цвет - на черный, заодно заполняем массив MAnd}
  for m := 31 downto 0 do
    for n := 0 to 31 do
    begin
      MPix[a] := Image2.Canvas.Pixels[n, m];
      p := MPix[a];
      if p = RGB(255, 255, 255) then
      begin
        MPix[a] := RGB(0, 0, 0);
        if b = 0 then
          Bt := Bt + 128;
        if b = 1 then
          Bt := Bt + 64;
        if b = 2 then
          Bt := Bt + 32;
        if b = 3 then
          Bt := Bt + 16;
        if b = 4 then
          Bt := Bt + 8;
        if b = 5 then
          Bt := Bt + 4;
        if b = 6 then
          Bt := Bt + 2;
        if b = 7 then
          Bt := Bt + 1;
      end;
      b := b + 1;
      if b = 8 then
      begin
        MAnd[c] := Bt;
        Bt := 0;
        c := c + 1;
        b := 0;
      end;
      a := a + 1;
    end;
  {Заполнение маски MOrg нулями, предполагая 256 цветов}
  SetLength(MOrg, 256);
  for n := 0 to 255 do
    MOrg[n] := RGB(255, 255, 255);
  {Оцениваем количество цветов и от результата переделываем MOrg}
  a := 0; //Счетчик количества цветов
  for n := 0 to 1023 do
  begin
    p := MPix[n];
    k := false;
    for m := 0 to 255 do
    begin
      if k = false then
        if p = MOrg[m] then
          k := true;
    end;
    if k = false then
    begin
      if a > 255 then
      begin
        ShowMessage('Ваш рисунок имеет более 256 цветов');
        MOrg := nil;
        MPix := nil;
        MAnd := nil;
        exit;
      end;
      MOrg[a] := p;
      a := a + 1;
    end;
  end;
  LenOsn := 2238;
  LenXor := 1024;
  LenCol := 1024;
  Indicator := 1;
  if a < 15 then
  begin
    LenOsn := 766;
    LenXor := 512;
    Indicator := 0;
  end;
  {Заполняем маски нулями}
  SetLength(MXor, LenXor);
  SetLength(MCol, LenCol);
  for n := 0 to LenXor - 1 do
  begin
    MXor[n] := 0;
  end;
  for n := 0 to LenCol - 1 do
  begin
    MCol[n] := 0;
  end;
  {Заполнение массива MXor согласно массива MPix}
  Bu := 0;
  b := 0;
  c := 0;
  for n := 0 to 1023 do
  begin
    p := MPix[n];
    k := false;
    for Bt := 0 to 255 do
    begin
      if k = false then
        if p = MOrg[Bt] then
        begin
          k := true;
          if indicator = 1 then
            MXor[n] := Bt
          else if b = 1 then
          begin
            Bu := 16 * Bu + Bt;
            MXor[c] := Bu;
            c := c + 1;
            b := 0;
          end
          else
          begin
            Bu := Bt;
            b := 1;
          end;
        end;
    end;
  end;
  {Заполняем массив MCol согласно массива MOrg}
  begin
    a := 0;
    for n := 0 to 255 do
    begin
      p := MOrg[n];
      MCol[a] := GetBValue(p);
      MCol[a + 1] := GetGValue(p);
      MCol[a + 2] := GetRValue(p);
      a := a + 4;
    end;
  end;
  {Заполняем MOsn нулями}
  SetLength(MOsn, LenOsn);
  for n := 0 to LenOsn - 1 do
    MOsn[n] := 0;
  {Заполняем массив MOsn для 256 цветной иконки}
  if indicator = 1 then
  begin
    MOsn[2] := 1;
    MOsn[4] := 1;
    MOsn[6] := 32;
    MOsn[7] := 32;
    MOsn[14] := $A8;
    MOsn[15] := 8;
    MOsn[18] := $16;
    MOsn[22] := $28;
    MOsn[26] := 32;
    MOsn[30] := $40;
    MOsn[34] := 1;
    MOsn[36] := 8;
    MOsn[42] := $80;
    MOsn[43] := 4;
    MOsn[55] := 1;
    m := 0;
    for n := 62 to 1085 do
    begin
      MOsn[n] := MCol[m];
      m := m + 1;
    end;
    m := 0;
    for n := 1086 to 2109 do
    begin
      MOsn[n] := MXor[m];
      m := m + 1;
    end;
    m := 0;
    for n := 2110 to 2237 do
    begin
      MOsn[n] := MAnd[m];
      m := m + 1;
    end;
  end;
  {Заполняем массив MOsn для 16-цветной}
  if indicator = 0 then
  begin
    MOsn[2] := 1;
    MOsn[4] := 1;
    MOsn[6] := 32;
    MOsn[7] := 32;
    MOsn[8] := 4;
    MOsn[14] := $E8;
    MOsn[15] := 2;
    MOsn[18] := $16;
    MOsn[22] := $28;
    MOsn[26] := 32;
    MOsn[30] := 64;
    MOsn[34] := 1;
    MOsn[36] := 4;
    MOsn[43] := 2;
    MOsn[54] := 16;
    m := 0;
    for n := 62 to 125 do
    begin
      MOsn[n] := MCol[m];
      m := m + 1;
    end;
    m := 0;
    for n := 126 to 637 do
    begin
      MOsn[n] := MXor[m];
      m := m + 1;
    end;
    m := 0;
    for n := 638 to 765 do
    begin
      MOsn[n] := MAnd[m];
      m := m + 1;
    end;
  end;
  {Закрываем все массивы, кроме MOsn}
  MAnd := nil;
  MXor := nil;
  MOrg := nil;
  MCol := nil;
  MPix := nil;
  {Записываем массив MOsn в файл, для этого создаем нетипизированный файл,
  активизируем его и побайтно пишем в него данные из массива MOsn}
  if SaveDialog1.Execute then
  begin
    AssignFile(f, SaveDialog1.FileName + '.ico');
    Rewrite(f, 1);
    for n := 0 to LenOsn - 1 do
    begin
      Bt := MOsn[n];
      BlockWrite(f, Bt, 1);
    end;
    CloseFile(f);
    MOsn := nil;
  end;
end;

Существуют иконки более сложной структуры. Они могут содержать несколько рисунков. Я с ними не разбирался.

Может быть кто-то из профессионалов скажет - примитивщина, нагородил. Ну что ж, может быть есть пути проще и лучше, но когда я спрашивал на форумах никто не смог помочь. Главное, работает без проблем. Я всего месяц как познакомился с Delphi и Паскалем, думаю можно кое-что и простить.

О структуре иконки. Информации немного в интернете, но кое-что я раскопал. Пришлось просмотреть коды разных иконок, чтобы иметь о них представление. Файл ICO очень похож на BMP: в начале файла заголовок BITMAPFILEHEADER, размером обычно 22 байта. В нем содержится информация о файле. За ним идет BITMAPINFOHEADER, 40 байт, содержит информацию о ширине и высоте иконки в пикселах, количество бит на пиксел в структуре картинки, занимаемое место таблицей цветов и другая информация. Для примера оба заголовка для 16-цветной иконки. В квадратных скобках номер байта в файле, далее эго значение.

[2]  := 1;   // Всегда 1 
[4]  := 1;   // Всегда 1 
[6]  := 32;  // Ширина и 
[7]  := 32;  // высота в пикселах? 
[8]  := 4    // Не знаю 
[14] := $E8; // Младший байт размера файла минус 22(размер BITMAPFILEHEADER) 
[15] := 2;   // Старший байт размера файла 
[18] := 22;  // Размер BITMAPFILEHEADER 
[22] := 40;  // Размер заголовка BITMAPINFOHEADER 
[26] := 32;  // Ширина в пикселах 
[30] := 64;  // Высота в пикселах обоих масок в сумме 
[34] := 1;   // Число плоскостей 
[36] := 4;   // бит/пиксел для таблицы пикселов 
[43] := 2;   // Старший байт размера таблицы пикселов (т.е.$200=512) 
[54] := 16;  // Число используемых цветов (не обязательно) 

Остальные не указанные байты и до адреса 62 все 0. После заголовков находится таблица цветов.

Каждый цвет занимает 4 байта. По порядку - синий, зеленый, красный и нулевой байты. Первые 3 байта образуют цвет пиксела. То есть, каждый пиксел на иконке может иметь 256х256х256 оттенков. При 16 цветной иконке таблица цветов занимает 16х4=64 байта, при 256 цветной - 1024 байта. По идее, при записи иконки любая программа должна просканировать иконку и все найденные цвета записать в таблицу.Почему этого не происходит я не знаю. Windows эти цвета при записи игнорирует и устанавливает свои. Если же при 16 цветной палитре используются только, допустим, 6 цветов, то остальные 10 цветов не нужны, однако место для них остается, но оно не используется.

Следующим блоком в файле идет таблица пикселов. Первый пиксел это самый нижний в левом углу. И переписываются они в таблицу построчно. Основной параметр, по которому таблица организуется, это количество бит на пиксел. Каждый элемент этой таблицы - это номер цвета в таблице цветов. При выводе на экран иконки берется очередной элемент, то есть номер цвета в таблице цветов, берет по номеру цвет из таблицы цветов и этот цвет выводится на экран. Так как для определения номера в таблице из 16 цветов достаточно 4 бит, то каждый байт таблицы пикселов определяет 2 пиксела. Причем, младшие 4 разряда относятся к первому, а старшие - к следующему пикселам. Для 256 цветов необходим полный байт. Поэтому, таблица пикселов занимает соответственно 512 и 1024 байтов. Таблица пикселов это образ маски XOR иконки, цвет, который должен быть прозрачным, заменен черным, в остальном же это рисунок, который вы создали в редакторе.

И последним идет таблица маски AND. Это двухцветная маска вашего рисунка, имеет только белый цвет (бит включен) и черный цвет (бит равен 0). Белый цвет находится там, где будет прозрачный цвет, черный скрывает все остальное. При наложении двух масок друг на друга тот пиксел, у которого на маске AND белый, а на маске XOR черный цвет, будет невидим. Так как маска AND монохромная, то один байт содержит информацию о 8 пикселах, а размер маски будет 1024/8=128 байт. Имеем:

для 16-цветной иконки размер 22+40+64+512+128=766 байт,
для 256 - 22+40+1024+1024+128=2238 байт.

Создав массив из соответствующим образом пребразованного рисунка, мы можем записать его байты в нетипизированный файл, которому все равно, что в нем находится, добавив к имени расширение .ico, мы получим то, что было нужно. Конечно, есть разные варианты размеров иконок, у них могут меняться размеры заголовков, использоваться другая информация, но структуры эти должны присутствовать. Существуют иконки гораздо большие размером, но я с такими не разбирался.






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook