altНу что ж, в предыдущих статьях мы рассмотрели основы работы с библиотекой KOL, написали первое функциональное приложение, рассмотрели основы создания обработчиков событий и прочее. Конечно стандартный GUI – это хорошо, но все- же приукрасить его не помешало бы. Например, создать панель инструментов, наделив кнопочки на ней соответствующими пиктограммами. А может и приукрасить форму какими-нибудь изображениями, сделать какой-нибудь задний фон ну или что-нибудь в этом роде. Именно поэтому тема сегодняшней моей статьи – работа с графикой и графическими файлами средствами KOL. Начнем, как всегда с теории, а закончим прикольным примером реализации полученных знаний на практике.

Теоретическая часть

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

Теперь разберемся с объектами, предназначенными для обработки изображений. Основным объектом, который используется для работы с графикой в KOL, является PBitmap. Все остальные, позволяющие работать с другими форматами изображений, так или иначе основываются на нем.

Конструктор объекта PBitmap можно вызвать с помощью функции:
function NewBitmap( W, H: Integer ): PBitmap;

W и H, как вы наверное уже догадались, задают размеры нового изображения. Все свойства PBitmap аналогичны своему аналогу из VCL – TBimap. Тут так же поддерживается канва (Canvas), базовые функции рисования, загрузка и сохранение изображений из файлов и потоков. Вообще любой контрол из библиотеки KOL, имеющий свойство Canvas, поддерживает базовые графические операции.

Еще в PBitmap присутствуют дополнительные крайне полезные функции вроде поворота изображений или отражения по вертикали/горизонтали. Для поддержки других графических форматов, например JPEG или GIF, потребуется подключить дополнительные модули (JpegObj и KolGif соответственно). Если же и этого мало, то советую скачать «пакет» KOLGraphic. С его помощью можно оперировать с форматами вроде *.pcx; *.dcx, *.pcc; *.scr, *.png, *.bw, *.rgb, *.rgba, *.sgi, *.tif, *.tiff и кучей других. Но поскольку я рассматриваю основы работы с графикой, то касаться данного пакета я не буду – вы всегда можете это сделать сами, скачав его с официально сайта (http://kolmck.net/Components/graphics/KOLGraphic.zip).

Хорошо, допустим, картинку мы загрузили. Теперь надо бы ее где-то отрисовать. Для этого я использую компонент PaintBox, конструктор которого выглядит следующим образом:
function NewPaintbox( AParent: PControl ): PControl;

В принципе, рисовать можно на любом контроле, имеющим свойство «Canvas». Для того, чтобы изображение появилось на PaintBox, нужно указать процедуру, обрабатывающую событие OnPaint. Заголовок процедуры должен быть следующим:
procedure Proc_Name (Dummy: Pointer; Sender: PControl; DC: HDC );

Рисование происходит с помощью функции Draw (или ей схожих- вроде StretchDraw, DrawTransparent или StretchDrawTransparent). Рассмотрим все это дело на примере.

Создадим новый проект, очистим его от форм и лишнего кода в .dpr файле проекта. Опишем несколько переменных:
var
MainForm,
PBox: PControl;
ImgContainer: pBitmap;
mainMenu: PMenu;
OpenDialog : POpenSaveDialog;
MainForm – наша главная форма, PBox – PaintBox, где будет выводится изображение, ImgContainer – контейнер для изображения, обычный TBitmap. mainMenu – меню программы, OpenDialog – диалог для выбора графического файла. Теперь закодим создание формы:
MainForm:=NewForm(Applet,’Simple test’).SetSize(650,600);
MainForm.CenterOnParent;
MainForm.Font.FontName := ‘MS Sans Serif’;
MainForm.Font.FontHeight := 9;
Функция CenterOnParent размещает нашу форму в середине экрана. Заодно сразу настроим шрифт приложения, чтобы он не был стандартным. Все компоненты, которые будут созданы на форму, по дефолту примут шрифт формы-родителя (только если вы вручную не поменяете его). Теперь инициализируем контейнер для изображения и создадим меню и его обработчик:
ImgContainer:= NewBitmap(0,0);
// Создадим меню
mainMenu := NewMenu(MainForm,0,
// Заполним пункты
[ ‘&Файл’,
‘(‘,
‘&Открыть картинку’,
‘-‘,
‘&Выход’,
‘)’],
// Обработчик нажатия пунктов меню
TOnMenuItem( MakeMethod(nil, @ProcessMenu )));
В нашем простом примере в меню будет только два пункта: «Открыть картинку» и «Выход». Обработчиком нажатий пунктов меню будет процедура ProcessMenu. Код ее выглядит следующим образом:
procedure ProcessMenu(P: Pointer; Sender:PMenu; Item:Integer);
begin
case Item of
1: OpenPicture;
3: Applet.Close;
end;
end;
В принципе ничего сложного. Весь код почти идентичен тому, что я писал в предыдущих статьях. Как видите, пункт 1 вызывает выполнение процедуры OpenPicture, которая открывает изображение. Опишем тело это процедуры:
procedure OpenPicture;
begin
if OpenDialog = nil then
OpenDialog := NewOpenSaveDialog(‘Открыть рисунок’,»,[]);
OpenDialog.Filter:=’Bitmap Pictures|*.bmp’;
if OpenDialog.Execute then
begin
LoadPicture(OpenDialog.Filename);
end;
end;
Изначально проверяем, не был ли создан объект OpenDialog ранее. Если нет, то вызываем его конструктор, и настраиваем фильтры. В нашем случае будем загружать только картинки BITMAP формата. После если диалог выбора файла был запущен, произведен вызов процедуры LoadPicture, передав ей имя выбранного файла. Код функции выглядит так:
procedure LoadPicture(const FName: String);
begin
if FileExists(FName) then
begin
ImgContainer.LoadFromFile(FName);
PBox.Width := ImgContainer.Width;
PBox.Height:= ImgContainer.Height;
Applet.ClientWidth:=Bitmap.Width + Applet.Border * 2;
Applet.ClientHeight:=Bitmap.Height + Applet.Border * 2;end;
ImgContainer.Draw(PBox.Canvas.Handle, 0, 0);
end;
end;
Поясню немного код. Как и обычный TBimap, PBitmap может загружать изображения из файлов, потоков и ресурсов. Мы воспользуемся первым способом. Далее настроим размеры PaintBox и главной формы под размеры загруженной в ImgContainer картинки. И функцией Draw отрисуем изображение в PaintBox. Теперь вернемся к основному телу программы. После создания меню, надо бы и сам PaintBox создать, на который выводятся изображения. Как его создавать ты уже должен знать, если не пропустил теоретическую часть моей статьи:
// Объект, на котором будут рисоваться изображения
PBox := NewPaintBox(MainForm);
PBox.SetSize(1,1);
PBox.OnPaint := TOnPaint( MakeMethod( nil, @ProcessImage ) );
Обработчиком OnPaint я назначил процедуру ProcessImage. Она производит перерисовку картинки на компоненте, а код ее выглядит следующим образом:
procedure ProcessImage(Sender: PControl; DC: HDC );
begin
ImgContainer.Draw(PBox.Canvas.Handle,0,0);
end;
Ну вот основной код написан, осталось произвести запуск нашего приложения, для чего напишем последней строчкой в основном теле приложения Run(MainForm). Компилируем проект и запускаем. У меня получилось вот так:



Что ж, вполне неплохо. Ради интереса посмотрим на размер компилированного проекта: 35 Кб! Отлично, теперь можно попробовать обработать GIF изображения.

Не Bitmap-ом едины!

Кстати говоря, KOL поддерживает как обычные, так и анимированные GIF изображения. Для их поддержки требуется скачать модуль KOLGif (http://kolmck.net/Components/graphics/kolgif.zip). Распаковываем архив куда-нибудь и подключаем KOLGif.pas к нашему проекту, который мы рассмотрели и написали чуть выше. Конструктор GIF изображения выглядит следующим образом:
function NewGif: PGif;

В данном случае PGif – это и есть объект, который хранит GIF изображение. Именно такой тип задается переменной, из которой в последствии будем загружать картинку. Так же есть еще один способ хранения GIF – это использовать PGifDecoder. Данный тип используется основным PGif как контейнер для данных. Однако мы можем им воспользоваться в целях экономии размера готовой программы, не нагружая ее дополнительными функциями из PGif. Разница получается не особо весомая – 5 Кб, но для тех, кому размер крайне важен, и такой размер может сыграть значительную роль. Тем более, что цель программирования в Delphi с использованием KOL – это уменьшение размера бинарника. Конструктор PGifDecoder выглядит, как и все конструкторы, в KOL так:
function NewGifDecoder: PGifDecoder;

Он также поддерживает загрузку изображений из файлов, потоков и ресурсов. Однако вызвать функцию Draw в нем так просто не получится. Для этого надо обратиться к свойству Bitmap, а от него к процедуре Draw. То есть у нас получится следующий код, рисующий на канве контрола (у нас это PaintBox):
G:= NewGifDecoder;
G.LoadFromFile(‘Test.gif’);
G.Bitmap.Draw(PaintBox.Canvas.Handle, 0, 0);
Все в принципе просто и не должно вызывать трудностей. Попробуем немного изменить текст нашей прошлой программы, добавив в нее поддержку GIF изображений. Для этого требуется:

1. Добавить переменную GIF типа PGif и Ext типа String
2. Вызвать конструктор для данной переменной в основном теле программы
3. Добавить код рисования GIF изображения на PaintBox

Итак, следуя нашему плану, добавляем переменную:
var

GIF: PGif;
Ext: String;

Затем вызовем конструктор NewGif для переменной GIF после такого же конструктора, но только для Bitmap:

GIF:= NewGif;

Конечно, надо не забыть добавить в фильтры OpenDialog расширение *.gif, иначе весь наш труд коту под хвост. Далее надо подкорректировать код процедуры LoadPicture, добавив туда обработку GIF изображений. В связи с тем, что у нас для картинок две разные переменные, то и определять, какого формата изображение выбрал пользователь, придется нам самим. Для этого само просто – это сравнивать расширение открытого файла. Если это “.gif” – вызываем функцию Draw для переменной Gif, если “.bmp” – то для переменной Bitmap. Выглядит это следующим образом:
procedure LoadPicture(const FName: String);
begin
Ext:=ExtractFileExt(FName);
if FileExists(FName) then
begin
if AnsiUpperCase(Ext)=’.GIF’ then
begin
GIF.LoadFromFile(FName);
PBox.Width := GIF.Width;
PBox.Height:= GIF.Height;
GIF.Draw(PBox.Canvas.Handle, 0, 0);
end else
begin
Bitmap.LoadFromFile(FName);
PBox.Width := Bitmap.Width;
PBox.Height:= Bitmap.Height;
Bitmap.Draw(PBox.Canvas.Handle, 0, 0);
end;
Applet.ClientWidth:=PBox.Width + Applet.Border * 2;
Applet.ClientHeight:=PBox.Height + Applet.Border * 2;
end;
end;
Если Вы заметили, что переменная Ext – глобальная. И она постоянно хранит расширение последнего выбранного в OpenDialog файла. Данная переменная так же помогает в отрисовке изображения на контроле, так как в процедуре ProcessImage – обработчике OnPaint для PaintBox – вызывается функция Draw для определенной переменной. Идея корректировки данной процедуры такая же, как и у LoadPicture:
procedure ProcessImage(Sender: PControl; DC: HDC );
begin
if AnsiUpperCase(Ext)=’.GIF’ then
GIF.Draw(PBox.Canvas.Handle,0,0)
else
Bitmap.Draw(PBox.Canvas.Handle,0,0)
end;
Ну вот и все, немного «моддинга» и наше приложение поддерживает GIF изображения! Компилируем проект и получаем программку размером 50 Кб. Окно ее выглядит так же, как и в прошлом примере, потому я не буду делать его скриншот – поменялась только «начинка» нашего приложения.

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

А сейчас можно приступить к обещанному мною ранее: теперь займемся написание нормального, функционального приложения. А по ходу изучим еще и несколько отличных, полезных функций, которые могут пригодиться при разработке глобального проекта. А писать мы будем замену ACDSee =). Да-да, именно программу для просмотра и манипуляции с графическими файлами – ведь это напрямую касается темы моей сегодняшней статьи. Что зря время терять, приступим!

VR Image Viewer

Наша программа будем обладать многими полезными фишками, которыми наделены большинство программ для просмотра изображений. Это:

1. Открытие изображений и вывод на экран
2. Сохранение изображений
3. Копирование/вырезание/удаление графических файлов
4. Навигация по папке с изображениями
5. Функции поворота изображения
6. Вывод информации об изображении
7. Я не стал нагружать приложение поддержкой кучи разных графических форматов — это будет ваше домашнее задание. Поэтому все манипуляции будут проходить над Bitmap картинками.

Создадим новый проект. Удалим все ненужные формы, оставим лишь .dpr файл проекта. Уберем весь код между begin и end. Из подключенных модулей оставим лишь Windows и KOL. Более нам ничего не потребуется. Теперь объявим нужные переменные:
var
MainForm,
PBox, TB,
SBox: PControl;
ParamString :string;
i: integer;
Num: Integer = 0;
ImgContainer: PBitmap;
mainMenu: PMenu;
OpenDialog : POpenSaveDialog;
OpenDirDialog : POpenDirDialog;
ImageList: PStrList;
Немного пробежимся по ним. Ну, MainForm понятно – главная форма приложения. PBox – это компонент PaintBox, на нем будет отрисовываться открытое изображение. TB – это ToolBar, наша панель инструментов. SBox – ScrollBox, используется для полноценного просмотра изображений, больших по размеру, чем форма. ParamString будет хранить полное имя переданного в командной строке файла. I выступает за счетчик. Num – хранит номер открытого в данный момент изображения. ImgContainer – картинка формата Bitmap, сюда будет загружаться изображение. mainMenu – это меню нашей программы. OpenDialog – обычный диалог открытия/сохранения файла. OpenDirDialog – это диалог выбора папки. И, наконец, ImageList – аналог TStringList из VCL. В данном списке будет храниться список изображений определенной папки. Почему я решил делать именно так, а не через динамический массив? Целью было показать некоторые другие возможности библиотеки KOL, которые могут в будущем пригодиться при разработке программы.

Следующим шагом будет объявление констант, которые пригодятся по ходу программирования:
const
B_FIRST = 0;
B_PREVIOUS = 1;
B_NEXT = 2;
B_LAST = 3;
const
ABOUT_INFO = ‘VR Image Viewer 0.1’#13#13’Coded by BULKA, 2010’+
#13#13′-= Delphi with KOL =-‘#13#13’http://vr-online.ru’+
#13’http://bulaj.ru’;
Константы из первого блока отвечают за навигацию по списку изображений (ImageList). B_FIRST дает понять, что программа должна открыть первое в списке изображение, B_PREVIOUS – предыдущее по списку изображение (определяется через значение переменной Num), B_NEXT – следующее, а B_LAST – последнее в списке. Константа ABOUT_INFO пригодится при вызове пункта меню «О программе». Более манипулировать с различными переменными и константами мы не будем. Далее пойдет код функций и процедур, выполняющих все описанные выше задачи. Но к ним будем обращаться по ходу, а начнем, как обычно, с основного кода программы. Прежде всего, создадим форму приложения:
begin
MainForm:=NewForm(Applet,’VR Image Viewer 0.1 by Bulka’).SetSize(650,600);
MainForm.CenterOnParent;
MainForm.Font.FontName := ‘MS Sans Serif’;
MainForm.Font.FontHeight := 9;
Ок, создали, теперь настроим панель статуса. В ней будет отображаться различная информация об открытом изображении:
MainForm.StatusText[0]:=’No images loaded’;
MainForm.StatusText[1]:=’0 kb’;
MainForm.StatusText[2]:=’0x0′;
MainForm.StatusText[3]:=’0/0′;
В первой панельке будет отображаться имя открытого файла, вот второй – размер файла, в третьей – размеры изображения и кол-во битов на пиксель, в четвертой – номер открытого в данный момент изображения/кол-во всех изображений в папке. Далее создаем меню программы:
mainMenu := NewMenu(MainForm,0,
// Заполним пункты
[ ‘&Файл’,
‘(‘,
‘&Очистить экран’,
‘Открыть’#9’Ctrl+O’,
‘Сохранить как..’#9’Ctrl+S’,
‘-‘,
‘&Выход’,
‘)’,
‘Правка’,
‘(‘,
‘Повернуть по часовой’#9’Ctrl+R’,
‘Повернуть против часовой’#9’Ctrl+L’,
‘-‘,
‘Вырезать файл…’#9’Ctrl+X’,
‘Копировать файл…’#9’Ctrl+C’,
‘Удалить файл’#9’Del’,
‘)’,
‘Просмотр’,
‘(‘,
‘Вперед’#9’Right’,
‘Назад’#9’Left’,
‘)’,
‘&Помощь’,
‘(‘,
‘О проге’,
‘)’ ],
// Обработчик нажатия пунктов меню
TOnMenuItem( MakeMethod(nil, @ProcessMenu )));
Все эти функции Вы уже должны знать из предыдущих моих статей, тут ничего нового. Инициализировали меню, заполнили его, присвоили обработчик пунктов меню. Однако в этот раз, если вы заметили, возле некоторых пунктов меню присутствует дополнение вроде #9’Ctrl+C’ и подобного. Все верно, в этой статье я покажу, как добавить горячую клавишу к определенному пункту меню. Это делается функцией MakeAccelerator:
function MakeAccelerator( fVirt: Byte; Key: Word ): TMenuAccelerator;

Первым параметром передается код виртуальной клавиши, вторым – код обычной клавиши на клавиатуре. В результате функция возвращает запись (record) TmenuAccelerator, где хранится все эта информация. Относительно меню, мы будем применять вызов данной функции для задания параметра ItemAccelerator у любого пункта меню. Делается это следующим образом:
MainMenu.ItemAccelerator[2]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘O’));
MainMenu.ItemAccelerator[3]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘S’));
MainMenu.ItemAccelerator[7]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘R’));
MainMenu.ItemAccelerator[8]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘L’));
MainMenu.ItemAccelerator[10]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘X’));
MainMenu.ItemAccelerator[11]:= MakeAccelerator(FCONTROL or FVIRTKEY, Word(‘C’));
MainMenu.ItemAccelerator[12]:= MakeAccelerator(FVIRTKEY, VK_DELETE);
MainMenu.ItemAccelerator[14]:= MakeAccelerator(FVIRTKEY, VK_RIGHT);
MainMenu.ItemAccelerator[15]:= MakeAccelerator(FVIRTKEY, VK_LEFT);
В квадратных скобочках мы указываем номер пункта меню, которому задаем горячую клавишу. Если посмотреть на код создания меню, можно увидеть, что пункту «Открыть» присвоено сочетание Ctrl (FCONTROL) + O (Word(‘O’)). Таким же образом задаются горячие клавиши и для других пунктов меню.

Далее в коде создадим «контейнер» для картинок и список, где будут храниться их полные имена:
ImgContainer := NewBitmap(0, 0);
// Создадим список файлов определенного каталога
ImageList:= NewStrList;
Затем следует создать панель инструментов. Если Вы помните, то это переменная TB. Конструктор панели инструментов выглядит следующим образом:
function NewToolbar( AParent: PControl; Align: TControlAlign; Options: TToolbarOptions; Bitmap: HBitmap; const Buttons: array of PKOLChar; const BtnImgIdxArray: array of Integer ) : PControl;

Пройдемся по параметрам по порядку. Aparent – это контрол-родитель, на котором будет размещена панель. Align – расположение панели, например по верху (caTop), по правой части контрола (caRight) или по нему всему (caClient). Параметр Options задает дополнительные параметры настройки панели. Это может быть прозрачность (tboTransparent), расположение текста на кнопках (tboTextRight или tboTextBottom), вид панели (tboFlat) и прочее.

Bitmap – это изображение, из которого будут браться картинки для кнопок на панели инструментов. Константа Buttons создает кнопки и разделители. В зависимости от текста, будет создана просто кнопка (‘’), кнопка с текстом (‘xxx’)или разделитель (‘-‘). Константа BtnImgIdxArray задает для созданных ранее через Buttons кнопок номера изображений, которые берутся из параметра Bitmap, если он указан. Если на панели присутствуют разделители, то для них стоит указывать параметр -1. Ну вот и все. Теперь посмотрим на код создания нашей панели инструментов:
TB := NewToolbar(MainForm, caNone, [tboFlat], CreateMappedBitmap( hInstance, 111, 0, nil, 0 ), [
», ‘-‘,
», », ‘-‘,
»,»,», ‘-‘,
», », », »,
‘-‘, », », ‘-‘, »],
[ 0, -1, 1, 2, -1, 3,4,5,-1, 6,7, 8, 9, -1, 10, 11, -1, 12] ).SetAlign(caTop);
TB.DoubleBuffered := True;
// Присовим пунктам подсказки
TB.TBSetTooltips( TB.TBIndex2Item(0), [ ‘Clear’,», ‘Open’, ‘Save’, », ‘Cut’, ‘Copy’,’Delete’, »,
‘To first’, ‘Previous’,’Next’, ‘To last’, »,
‘Rotate left’, ‘Rotate right’, », ‘About’ ] );
TB.OnClick := TOnEvent(MakeMethod(nil, @TBClick));
Функцией CreateMappedBitmap мы загрузили из ресурсов программы (hInstance) изображение под именем 111. В нем нарисованы изображения для кнопочек. Оно взято из файла Toolbar.bmp, который лежит в папке «VR Image Viewer». Для того, чтобы добавить картинку в ресурсы программы, я создал .RC файл, прописал в нем имя ресурса (111), тип ресурса (BITMAP) и путь к файлу с изображением. Затем подключил данный файл к проекту. Функция TBSetTooltips задает подсказки для кнопок на панели. В качестве первого параметра я указал TB.TBIndex2Item(0), чем показал функции, с какого номера начинать вставлять подсказки (в моем случае это первая кнопка, индекс равен 0). Обработчиком нажатий кнопок на панели является функция TBClick. Ее код я опишу позже, когда завершим основное тело программы.

Итак, следующим нашим шагом будет создание ScrollBox и «холста» для рисования изображения (PaintBox). Сделаем это, вызвав конструкторы NewScrollBoxEx и NewPaintBox:
SBox := NewScrollBoxEx(MainForm,esRaised).SetAlign(caClient);
// Объект, на котором будут рисоваться изображения
PBox := NewPaintBox(SBox);
PBox.SetSize(1,1);
PBox.OnPaint := TOnPaint( MakeMethod( nil, @ProcessImage ) );
PBox.Transparent:= True;
Почему я вызвал для переменной SBox конструктор NewScrollBoxEx, а не NewScrollBox, как это делаю обычно? Смысл данного действия прост – в обычном конструкторе надо задавать полосы прокрутки, а в Extended (отсюда и приставка Ex) они создается автоматически, если требуется. Обработчика переменной SBox нам не надо присваивать, а вот для PaintBox мы укажем процедуру ProcessImage, которая будет рисовать на контроле изображение из ImgContainer.

И последний штрих – обработка параметров командной строки при запуске программы:
// Если программе передано изображение, загрузим
if (ParamCount > 0) then
begin
ParamString:=ParamStr(1);
for i:=2 to ParamCount do
begin
ParamString:=ParamString+’ ‘+ParamStr(i);
end;
if ParamString» then
begin
ScanForFiles(ExtractFilePath(ParamString));
DrawPicture (ParamString);
Num:=ImageList.IndexOf(ParamString);
ShowCurrentImageNumber;
end;
end;
Run(MainForm);
Ну вот и все, основное тело приложения полностью готово. Немного поясню последние действия. При запуске программы проверяется, есть ли переданные параметры (ParamCount). Если есть, то начинаем собирать полный путь к файлу в цикле, так как пробел разделяет строку на несколько частей. В итоге полный путь к файлу записан в переменной ParamString. Далее происходит сканирование каталога на наличие изображений при помощи процедуры ScanForFiles. После вызываем процедуру DrawPicture, которая рисует на PaintBox картинку. При этом в переменную Num записывается номер данного изображения по счету из списка ImageList. И, вызывая процедуру ShowCurrentImageNumber, указываем в панели статуса номер открытой картинки.

Что ж, теперь приступим к написанию основных процедур и функций программы. Вот их список:

1) Slash
2) ProcessImage
3) ShowCurrentImageNumber
4) Rotate
5) DrawPicture
6) ScanForFiles
7) OpenPicture
8) SavePicture
9) ClearPicture
10) DeletePicture
11) CopyMovePicture
12) ProcessNavigation
13) ProcessMenu
14) TBClick

Не много, не мало :). Напишем код для каждой из вышеуказанных функций и процедур, и походу разберемся, что они делают.

Первой в нашем списке идет функция Slash, которая проверяет путь на наличие в конце «слеша». Если его нет, то он дописывается. Код функции выглядит следующим образом:
function Slash(const Path: String): String;
begin
if Path[Length(Path)-1] ‘\’ then
Result := Path + ‘\’
else
Result := Path;
end;
Далее опишем процедуру ProcessImage. Она, как я ранее отмечал, является обработчиком события OnPaint контрола PaintBox. В коде процедуры происходит отрисовка изображения на компоненте:
procedure ProcessImage(Sender: PControl; DC: HDC );
begin
ImgContainer.Draw(PBox.Canvas.Handle,0,0)
end;
Теперь идет черед процедуры ShowCurrentImageNumber. Ее назначение я так же описывал выше – она выводит на панели статуса номер отображаемого на данный момент изображения в PaintBox. Код процедуры крайне компактный и выглядит так:
procedure ShowCurrentImageNumber;
begin
MainForm.StatusText[3]:=PChar(Int2Str(Num+1)+’/’+Int2Str(ImageList.Count));
end;
Следующая процедура – Rotate. Согласно функционалу нашего приложения, оно должно уметь поворачивать изображение. Именно этим занимается эта процедура. Для поворота картинки формата Bitmap в библиотеке KOL специально предусмотрены несколько функций:

1. RotateRight / RotateLeft
2. RotateRightMono / RotateLeftMono
3. RotateRight4bit / RotateLeft4bit (8, 16 bit)
4. RotateLeftTrueColor / RotateRightTrueColor

Думаю, пояснять их не стоит, сами названия говорят за себя. Мы воспользуемся универсальными, то есть RotateRight и RotateLeft. Код процедуры выглядит так:
procedure Rotate(Left: Boolean);
var Tmp: Integer;
begin
if Left then
begin
Tmp:= PBox.Width;
PBox.Width:= PBox.Height;
PBox.Height:= Tmp;
ImgContainer.RotateRight;
ImgContainer.Draw(PBox.Canvas.Handle,0,0);
end else
begin
Tmp:= PBox.Width;
PBox.Width:= PBox.Height;
PBox.Height:= Tmp;
ImgContainer.RotateLeft;
ImgContainer.Draw(PBox.Canvas.Handle,0,0);
end;
end;
Думаю, вы заметили, что процедуре передается параметр Left типа Boolean. Из кода видно, что если он установлен как true, то картинка поворачивается против часовой стрелки, а если false – по часовой. Для того чтобы изображение не обрезалось по краям при повороте, я меняю размеры PaintBox.

Следующая процедура – DrawImage. Она встречается довольно часто. Назначение – отрисовывать изображение на PaintBox и выводить в панели статуса информацию о нем. Код процедуры выглядит следующим образом:
procedure DrawPicture(const FName: String);
var InfoStr: String;
begin
if FileExists(FName) then
begin
ImgContainer.LoadFromFile(FName);
PBox.Width := ImgContainer.Width;
PBox.Height:= ImgContainer.Height;
InfoStr:= Int2Str(PBox.Width)+’x’+Int2Str(PBox.Height);
InfoStr:= InfoStr + ‘ (‘ + Int2Str(ImgContainer.BitsPerPixel) + ‘ bit)’;
MainForm.StatusText[0]:=PChar(ExtractFileName(FName));
MainForm.StatusText[1]:= PChar(Int2Str(FileSize(FName)) + ‘ bytes’);
MainForm.StatusText[2]:=PChar(InfoStr);
ImgContainer.Draw(PBox.Canvas.Handle, 0, 0);
end;
end;
Итак, для начала проверяем, не был ли удален файл и вообще существует ли он. Если все окей, то загружаем его в ImgContainer. Далее настраиваем размеры PaintBox под размеры открытой картинки. Формируем информацию о размере изображения и кол-ва битов на пиксель, записывая ее в переменную InfoStr. Функция Int2Str позволяет (аналогично функции IntToStr из SysUtils.pas) конвертировать число в строку. Далее выводим всю информацию на панель статуса (имя файла, его размер, информацию по размеру картинки). И, наконец, рисуем само изображение.

Теперь рассмотрим код процедуры ScanForFiles. Из названия должно быть понятно, что она занимается поиском файлов. Причем в определенном каталоге, который передается параметром Path. Для этого воспользуемся функциями FindFirstFile, FindNextFile и FindClose, которые являются аналогами функций FindFirst, FindNext и FindClose из SysUtils.pas. Информация о найденном файле будет храниться в переменной FindData типа TWin32FindData. Если файл найден, то добавляем его полное имя (FindData.cFileName) в наш список изображений (ImageList). Поиск файлов будет осуществляться до тех пор, пока переменная FileEx типа Boolean, в результате выполнения функции FindNextFile, не станет равна False. На деле все выглядит таким образом:
procedure ScanForFiles(Path: String);
var S: String;
FileEx: Boolean;
FindHandle : THandle;
FindData : TWin32FindData;
begin
FileEx:=True;
FindData.dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
FindHandle := FindFirstFile(PChar(Slash(Path)+’*.bmp’), FindData);
if FindHandle INVALID_HANDLE_VALUE then
begin
while FileEx do
begin
S:=FindData.cFileName;
ImageList.Add(Path+S);
FileEx:=FindNextFile(FindHandle,FindData);
end;
end;
FindClose(FindHandle);
Num:=0;
end;
Как я и говорил, наша программа будет искать только картинки с расширением .bmp. Но вы сами всегда можете расширить эту опцию.

Теперь опишем процедуру OpenPicture. Она производит отображение диалога открытия файлов, вызов процедур ScanForFiles, DrawPicture и ShowCurrentImageNumber. Код ее приведен ниже:
procedure OpenPicture;
begin
if OpenDialog = nil then
OpenDialog := NewOpenSaveDialog(‘Открыть рисунок’,»,[]);
OpenDialog.Filter:=’Bitmap Pictures|*.bmp’;
if OpenDialog.Execute then
begin
ImageList.Clear;
ScanForFiles(ExtractFilePath(OpenDialog.Filename));
Num:= ImageList.IndexOf(OpenDialog.Filename);
DrawPicture(OpenDialog.Filename);
ShowCurrentImageNumber;
end;
Free_And_Nil(OpenDialog);
end;
Изначально проверяем, был ли ранее создан наш «диалог». Если нет, то вызываем конструктор NewOpenSaveDialog. В качестве фильтра задаем Bitmap картинки. Далее вызываем отображение диалога. Если пользователь не нажал кнопку «Отмена», то производим очистку списка картинок (ImageList), сканирование каталога с выбранным файлом на наличие еще картинок (ScanForFiles(ExtractFilePath(OpenDialog.Filename))), после чего рисуем картинку на PaintBox (DrawPicture). По завершению всех действий освобождаем объект и подчищаем за ним память процедурой Free_And_Nil. Она может быть использована с этими же целями для любого объекта библиотеки KOL.

Код процедуры SavePicture почти идентичен вышеописанной процедуре. Только вместо сканирования каталога и загрузки изображения мы производим сохранение картинки из ImgContainer в файл, указанный в SaveDialog. Код данный процедуры выглядит следующим образом:
procedure SavePicture;
begin
if OpenDialog = nil then
OpenDialog := NewOpenSaveDialog(‘Сохранить рисунок’, », [OSOverwritePrompt]);
OpenDialog.Filter:=’Bitmap Pictures|*.bmp’;
OpenDialog.OpenDialog:= False;
if OpenDialog.Execute then
begin
ImgContainer.SaveToFile(OpenDialog.Filename);
end;
Free_And_Nil(OpenDialog);
end;
Следующая процедура – ClearPicture. Она производит очистку экрана от картинки. Для этого вызываем процедуру Clear объекта PBitmap (в нашем случае это переменная ImgContainer), после чего производим перерисовку изображения на PaintBox изменение его размера:
procedure ClearPicture;
begin
ImgContainer.Clear;
ImgContainer.Draw(PBox.Canvas.Handle,0,0);
PBox.SetSize(1,1)
end;
Следующей в нашем списке идет процедура DeletePicture. Она производит удаление файла открытой в данный момент картинки в корзину. Вообще, для этой цели в KOL присутствует функция DeleteFile2Recycle. Но я не стал ее использовать ввиду того, что она не делает запроса на подтверждение об удалении, что не хорошо. Потому я решил воспользоваться функцией DoFileOp:
function DoFileOp( const FromList, ToList: KOLString; FileOp: UINT; Flags: Word;
Title: PKOLChar): Boolean;
Данная функция универсальна: она позволяет копировать (FileOp = FO_COPY), перемещать (FileOp = FO_MOVE), переименовывать (FileOp = FO_RENAME) или удалять (FileOp = FO_DELETE) файлы (FromList и ToList). Поскольку я удаляю файл в корзину, то вторым параметром (ToList) будет пустая строка, а параметр FileOp будет равен FO_DELETE. В качестве флагов я укажу FOF_ALLOWUNDO (чтобы можно было отменить операцию) и FOF_SIMPLEPROGRESS (не показывать имена удаляемых файлов). После удаления файла следует убрать его и из нашего списка, так как он более не присутствует в каталоге. Выполним это процедурой Delete объекта PstrList, указав номер открытого изображения, который всегда хранится в переменной Num. И, наконец, чтобы на экране не оставалось удаленное изображение, откроем первую картинку из списка, выполним процедуру ProcessNavigation с параметром B_FIRST. Код процедуры DeletePicture выглядит следующим образом:
procedure DeletePicture;
begin
if ImageList.Count < 1 then Exit;
if DoFileOp(ImageList.Items[Num], », $0003, $0040 or $0100, ‘Deleting…’ ) then
begin
ImageList.Delete(Num);
ProcessNavigation(B_FIRST);
end;
end;
Далее опишем процедуру CopyMovePicture. Она предназначена для копирования или перемещения открытой картинки. Для того, чтобы понять, какое действие надо выполнить, процедуре передается параметр Copy типа Boolean. Если он равен true, то копируем изображение в указанную папку, если false – перемещаем. Чтобы выбрать папку, в которую будет копироваться или перемешаться изображение, создадим и вызовем диалог выбора папки – PopenDirDialog. Конструктор данного объекта выглядит так:
function NewOpenDirDialog( const Title: KOLString; Options: TOpenDirOptions ):
POpenDirDialog;
Первым параметром задаем заголовок диалога, вторым – набор настроек. Стоит учесть, что если файл мы перемещаем, то он более не присутствует в той папке, из которой было загружено изображение. Поэтому при перемещении файла обязательно надо удалять его из списка (ImageList) и открывать любой другой, я, например, открываю самый первый. Ниже написан код процедуры CopyMovePicture:
procedure CopyMovePicture(Copy: Boolean);
begin
if ImageList.Count < 1 then Exit;
if OpenDirDialog = nil then
OpenDirDialog := NewOpenDirDialog(‘Куда копировать/перенести рисунок:’, []);
if OpenDirDialog.Execute then
begin
if not Copy then
begin
MoveFile(PChar(ImageList.Items[Num]), PChar(Slash(OpenDirDialog.Path) + ExtractFileName(ImageList.Items[Num]) ));
ImageList.Delete(Num);
ProcessNavigation(B_FIRST);
end else
CopyFile(PChar(ImageList.Items[Num]), PChar(Slash(OpenDirDialog.Path) + ExtractFileName(ImageList.Items[Num]) ), true);
end;
end;
Окей, продолжим. Следующий «пациент» — процедура ProcessNavigation. Она производит навигацию по изображениям: открытие первого и последнего по списку, следующего или предыдущего от открытого в данный момент. Как определить, какую именно картинку открывать? Для этого процедуре передается параметр ID типа Byte, где указывается одна из констант, описанных мною в начале статьи. В любом случае, какая бы кнопка не была нажата (или пункт меню), мы так же меняем и значение переменной Num. Если вы забыли, то напомню, что она хранит в себе номер изображения по списку. Вот код данной процедуры:
procedure ProcessNavigation(ID: Byte);
begin
case Id of
B_FIRST:
begin
Num:=0;
DrawPicture(ImageList.Items[Num]);
ShowCurrentImageNumber
end;
B_PREVIOUS:
if (Num > 0)then
begin
Dec(Num);
DrawPicture(ImageList.Items[Num]);
ShowCurrentImageNumber
end;
B_NEXT:
if Num < ImageList.Count - 1 then
begin
Inc(Num);
DrawPicture(ImageList.Items[Num]);
ShowCurrentImageNumber
end;
B_LAST:
begin
Num:= ImageList.Count — 1;
DrawPicture(ImageList.Items[Num]);
ShowCurrentImageNumber
end;
end;
end;
Теперь напишем процедуру ProcessMenu – обработчик нажатия пунктов меню. В нем используются все вышеописанные процедуры, поэтому тут нечего пояснять. Код предельно прост:
procedure ProcessMenu(P: Pointer; Sender:PMenu; Item:Integer);
begin
case Item of
1: ClearPicture;
2: OpenPicture;
3: SavePicture;
5: Applet.Close;
7: Rotate(true);
8: Rotate(false);
10: CopyMovePicture(false);
11: CopyMovePicture(true);
12: DeletePicture;
14: ProcessNavigation(B_NEXT);
15: ProcessNavigation(B_PREVIOUS);
17: MessageBox(MainForm.Handle, ABOUT_INFO, ‘Info’, MB_OK + $000040);
end;
end;
И последняя процедура – TBClick. Это обработчик нажатия кнопочек на нашей панели инструментов. Каждая кнопка имеет свой Id или, проще говоря, номер по счету. Учтите, что разделитель – эта та же кнопка, только не функциональная. Поэтому при подсчете номер учитывайте и его. С помощью параметра Sender типа Pcontrol мы узнаем Id нажатой кнопки, вызывая функцию CurIndex. И в зависимости от полученного значения вызываем нужную процедуру. Код процедуры выглядит так:
procedure TBClick(P: Pointer; Sender: PControl );
var Idx: Integer;
begin
Idx := Sender.CurIndex;
if idx < 0 then Exit;
if idx = 0 then ClearPicture;
if Idx = 2 then OpenPicture;
if Idx = 3 then SavePicture;
if Idx = 5 then CopyMovePicture(false);
if Idx = 6 then CopyMovePicture(true);
if Idx = 7 then DeletePicture;
if idx = 9 then ProcessNavigation(B_FIRST);
if idx = 10 then ProcessNavigation(B_PREVIOUS);
if idx = 11 then ProcessNavigation(B_NEXT);
if Idx = 12 then ProcessNavigation(B_LAST);
if Idx = 14 then Rotate(false);
if Idx = 15 then Rotate(true);
if Idx = 17 then MessageBox(MainForm.Handle, ABOUT_INFO, ‘Info’, MB_OK + $000040);
end;
Вот и все. Все основные процедуры и функции написаны, константы и переменные объявлены, тело программы присутствует. Производим компиляцию проекта, запускаем его и радуемся :).

Заключение

В результате запуска готовой программы и открытия первого попавшегося изображения у меня получилось такое окошко, приведенное на скриншоте ниже.

Размер готовой программы – 56 Кб, что для программы подобного рода и с таким функционалом крайне мало! И если использовать для работы с картинками модуль Graphics.pas, то размер бинарника увеличивается до 128 Кб, что уже существенно, не говоря о том, что некоторые функции, использованные в нашей программе (я говорю про вращение изображения) там вовсе отсутствуют. Все исходные коды рассмотренных мною сегодня программ прилагаются к статье.

Хотелось бы пожелать Вам удачи в кодинге. Если возникнут какие либо вопросы – обращайтесь! Ну а в следующей статье мы научимся работать с реестром и прочими радостями KOL. На этом все, чао.

News Reporter