Основы создания графического редактора типа Paint в Delphi

Источник: delphisources
Роман Радер

Ключевые вопросы этой статьи:
1. Что использовать для редактора?
2. Какими инструментами пользоваться?
3. Какие приемы используются?

Сделаем редактор с 5 инструментами, которые показывают основные технологии: карандаш, прямоугольник, эллипс, ломаная, заливка.

Начнем.

Фаза 1: Проектируем форму

   Основным элементом на форме будет, конечно же, картинка. Есть несколько вариантов компонентов для нее. Это Image, PaintBox и другие со свойством Canvas.
   Остановим свой выбор на PaintBox"е.
   Так же нужно не забывать, что картинка может быть больше, чем размеры формы. Для решения этой проблемы, используем ScrollBox.
   Для панели инструментов выберем нижнюю часть окна - установим Panel.
   Кнопки на панели - SpeedButton.

Итак:
1. Устанавливаем Panel, свойства Align=alBottom, Height=65.
2. На панель ставим 5 штук SpeedButton. У всех свойство GroupIndex=1 или другому числу. Главное, чтоб было одинаково. Одной из кнопок назначим Down=True
3. 2 штуки ColorBox на панель инструментов, в свойстве Style - cbPrettyNames=True. Второму присвоим Selected=clWhite.
4. Подпишем их "Цвет" и "Фон" соответственно с помощью Label.
5. Устанавливаем на форму ScrollBox, ставим у него свойство Align=alClient.
6. Внутрь ScrollBox ставим PaintBox. Ставим свойства Top=0 и Left=0, Align=alNone.
7. Также сделаем меню. Поставим MainMenu, сделаем в нем 1 меню - "Файл" с пунктами: Открыть, Сохранить, Выход.

Вот то, что вышло у меня после этих действий:
 

Окно программы в режиме конструктора

Окно программы в режиме конструктора
 


Фаза 2: Программирование

   Форму мы спроектировали. Теперь можно начать писать непосредственно программу.
   Рисовать мы будем не на холсте PaintBox"a, как можно подумать сначала, а в памяти. На холст будем выводить лишь результат работы.

Для этого объявим глобальные переменные:

img, buffer: TBitmap;
x0,y0: integer;

   Также объявим переменную dwn: boolean, которая будет говорить нажата левая кнопка или нет (рисовать или нет).

Объявим тип TShape = (sPen, sRect, sEllipse, sPoly, sFill).

   И глобальную переменную nowdrawing: TShape. В ней будет хранится тип фигуры, которую мы рисуем.

В событии формы OnCreate напишем:
 

 Img:=TBitmap.Create;
 buffer:=TBitmap.Create;
 img.Width:=PaintBox1.ClientWidth;
 buffer.Width:=PaintBox1.ClientWidth;
 img.Height:=PaintBox1.ClientHeight;
 buffer.Height:=PaintBox1.ClientHeight;
 nowdrawing:=sPen;
 dwn:=false;


   В событии OnMouseDown PaintBox"a проверяем, нажата ли левая кнопка. Если да, то устанавливаем значение nowdrawing"a в нужное, а также сохраняем текущую картинку и начальные координаты мыши. Если это заливка, то нам не нужно учитывать движение мыши, нам достаточно одного нажатия. Поэтому, если заливка, то снимаем флаг dwn, чтоб не реагировать на движения.

   Если мы рисуем ломаную линию, то реагировать нужно и на правую кнопку: для создания нового узла. Т.е. для рисования ломанной нужно держать левую кнопку нажатой, а для создания узлов кликать правую кнопку.

Если нажата не левая кнопка, то начальные координаты просто переписываем (x0,y0).
 

 if button=mbLeft then begin
 img.assign(buffer);
 x0:=x; y0:=y;
 if SpeedButton1.Down then
 begin
 nowdrawing:=sPen;
 img.canvas.MoveTo(x,y);
 end else
 if SpeedButton2.Down then
 nowdrawing:=sEllipse else
 if SpeedButton3.Down then
 nowdrawing:=sRect else
 if SpeedButton4.Down then
 nowdrawing:=sPoly else
 if SpeedButton5.Down then
 nowdrawing:=sFill;
 dwn:=true;
 img.Canvas.Pen.Color:=ColorBox1.Selected;
 img.Canvas.Brush.Color:=ColorBox2.Selected;

 if nowdrawing=sFill then
 begin
 img.Canvas.FloodFill(x0,y0,img.Canvas.Pixels[x,y],fsSurface);
 buffer.Assign(img);
 dwn:=false;
 end
 end else
 begin
 if (dwn)and(nowdrawing=sPoly) then begin
 x0:=x;
 y0:=y;
 buffer.Assign(img);
 end;
 end;

 paintbox1.Canvas.CopyRect(bounds(0,0,img.Width,img.Height),
                    img.Canvas,bounds(0,0,img.Width,img.Height));
 


   В событии OnMouseMove самое интересное: если флаг dwn не включен, то выходим сразу же из процедуры.
   Восстанавливаем старый холст и по выбору рисуемой фигуры в nowdrawing соответственно рисуем линию, прямоугольник, эллипс или отрезок - часть ломаной на картинке img. В конце рисования, переносим его на холст PaintBox"a.
 

 if not dwn then exit;
 img.assign(buffer);
 case nowdrawing of
 sPen:begin
 img.Canvas.LineTo(x,y);
 buffer.Assign(img);
 end;
 sRect:begin
 img.Canvas.Rectangle(x0,y0,x,y);
 end;
 sEllipse:begin
 img.Canvas.Ellipse(x0,y0,x,y);
 end;
 sPoly:begin
 img.Canvas.MoveTo(x0,y0);
 img.Canvas.LineTo(x,y);
 end;
 sFill:begin
 //nothing.
 end;
 end;

 paintbox1.Canvas.CopyRect(bounds(0,0,img.Width,img.Height),
                    img.Canvas,bounds(0,0,img.Width,img.Height));


В событии OnMouseUp:
 

 if button=mbLeft then dwn:=false;
 buffer.Assign(img);


   И, в заключение, в событии OnPaint PaintBox"a напишем прорисовку картинки из буфера:
 

 paintbox1.Canvas.CopyRect(bounds(0,0,img.Width,img.Height),
                 buffer.Canvas,bounds(0,0,img.Width,img.Height));
 


   Вот и все! У нас есть костяк редактора. Можно легко добавлять новые функции, модифицировать старые.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=20382