GDI+ TCanvas

Скачать

Работая программистом отдела АСУТП металлургического комбината, мне приходилось много «рисовать». Визуализация технологических процессов, разнообразные графики, диаграммы и т.д. Став на зыбкий путь свободного художника, стал «рисовать» еще больше. Заказчик хотел свои неповторимые интерфейсы, видеть у себя то, чего нет у других. Поэтому, естественным образом, обратился к GDI+.

GDI+ является надстройкой над обычным GDI. Из чего следует два вывода:

  • GDI+ медленнее, особенно при использовании антиалиаса.
  • и GDI, и GDI+ прекрасно уживаются на одном контексте устройства, т.к. используют одни и те же вызовы GDI API. Т.е., можно рисовать свободно смешивая подходы.

В GDI+ совсем другой подход к рисованию. Если он прекрасно уживается с GDI на одном контексте устройства, то в коде уживчивость так себе, надо писать совершенно разные фрагменты – для одного так, для другого эдак.

Соответственно, совершенно разные философии кодинга. Очень хотелось в свое время найти такой потомок TCanvas, который оперировал бы теми же привычными Pen, Brush, Font… RoundRect, Arc, Ellipse, Rectangle…, но рисовал бы в GDI+. Не нашел, поэтому сделал свой. Класс TxIPCanvas – прямой потомок TCanvas.

Какие проблемы решает TxIPCanvas

  1. У каждого программиста, так или иначе связанного с «рисованием», имеется увесистый багаж разнообразных «рисовалок», т.е. набор функций и методов, которые получают параметром ACanvas : TCanvas и рисуют на нем что-нибудь ну прямо очень… Не хотелось бы отказываться от этих наработок, но и переписывать все в GDI+ как-то совсем не комильфо. В представленном TxIPCanvas эта проблема решена. Т.е. один и тот же код используется как для обычного GDI, так и для GDI+. Примеров на сайте множество (не прямо сейчас на момент запуска, но будет много), допустим, «Нахождение длины дуги эллипса».
  2. Некоторые операции в GDI+ сильно притормаживают, например, вывод с использованием антиалиаса, поэтому есть возможность оперативно включать/выключать этот режим. Имеет смысл отключать при «рисовании» прямоугольников, любых горизонтально/вертикальных линий. Они и так будут выглядеть хорошо. И включать при всех эллипсах, скругленных прямоугольниках, траекториях, дугах, секторах. SetAntiAlias — включает, ResetAntiAlias — отключает.
  3. Можно оперативно отключать/включать режим GDI+. Допустим, эту часть кода я пишу в стандартном GDI, а эту часть хочу в GDI+. За это отвечает свойство UseStandartGDI : boolean.
  4. Сильно упрощена работа с изображениями. Вместо достаточно объемного кода по загрузке изображения из TGraphic в TGPImage, используются методы «всего лишь одной строкой». «Земля и Апельсин: Исходники».
  5. Упрощено использование перьевых «плюшек» GDI+: стиль линии на любой толщине, а не только на единичной, как в стандартном GDI, использование разнообразных (и своих тоже) наконечников, и т.д. Градиентные/текстурные заливки при рисовании линий, надписей. Много радостей.
  6. Нет необходимости создавать во множестве экземпляры TGPPen, TGPBrush, TGPFont, как это обычно делается при рисовании в GDI+. Все они уже есть в TxIPCanvas. Более того, можно создавать именованные экземпляры этих классов, в любом количестве, ограниченном только здравым смыслом, и подключать их по имени. Это ускорит «объемные» отрисовки.
  7. Некоторые особенности реализации тех или иных фишек GDI+ спрятаны в более удобную упаковку. Например, если я хочу вывести текст в прямоугольнике, или нарисовать фигуру в прямоугольнике, используя градиентную кисть, координаты градиента автоматически позиционируются в этом прямоугольнике. В стандартном варианте надо указывать – где начинается область действия кисти, и не забывать об этом. Иначе рождается миф про «глючность».

Пример кисти для TxIPCanvas

К слову, о «фишках». Возьмем, допустим, арсенал текстурных кистей, где вместо цвета используется некое изображение, текстура. Например, при создании не утративших еще популярности круглых аватарок, можно просто указать нужный портрет для кисти и вывести эллипс. Как если бы вы просто указали цвет Brush’у. Все.

//-- указываем текстуру --------------------------------- 
  IPBrush.GPImage := img.GPImage; 
//-- все как в обычном канвасе -------------------------- 
  Ellipse(val); //-- рисуем эллипс
Пример использования текстурной кисти
//***********************************************************
//  Получить круглую аватарку по TGraphic
//***********************************************************
function CreaCircleAva(AImage : TGraphic; 
                       ASize  : Integer = 64;
                       ABorder: Integer = 3 ): TGraphic;
var bmp : TxIPBitmap;
    img : TxIPImage;
    rct : TRect;
    val : TRect;
    w,h : Integer;
begin
  result := nil;
  if (AImage = nil) or (ASize < 2) then exit; 
  //-- определимся с прямоугольником вывода -----------------
  //-- делать в любом случае надо, хоть GDI, хоть GDI+ 
  rct := Rect (0,0,AImage.Width,AImage.Height); 
  w := ASize; h := ASize; 
  if AImage.Width > AImage.Height then 
    w := round(w * AImage.Width / AImage.Height)
  else 
    h := round(h * AImage.Height / AImage.Width);
  rct := GetProportRect(rct,w,h);

  //-- битмап на котором будем рисовать ---------------------
  bmp := TxIPBitmap.Create(ASize,ASize);
  //-- картинка для кисти -----------------------------------
  img := TxIPImage.Create(AImage);
  
  with bmp.Canvas do begin
    val := ClipRect;
    InflateRect (val,-2,-2);
    rct := CenterInRect(val,rct);
    //-- возможности TxIPCanvas -----------------------------
    SetAntiAlias; // рисуем с антиалиасом -------------------
    //-- используем уже "текстурную" кисть ------------------
    IPBrush.BrushType := xbtTextureFill;
    //-- устанавливаем масштаб отображения текстуры ---------
    IPBrush.ScaleXY := GPPointF(rct.Width/AImage.Width,
                                rct.Height/AImage.Height);
    //-- указываем текстуру ---------------------------------
    IPBrush.GPImage := img.GPImage;
    //-- все как в обычном канвасе --------------------------
    Ellipse(val); //-- рисуем эллипс 
    if ABorder > 0 then begin
      //-- если указано сделать рамку - рисуем рамку --------
      Pen.Color := $00986342;  
      Pen.Width := ABorder;
      Brush.Style := bsClear;
      Ellipse(val);
    end;  
  end;
  //-- получение результата сразу в PNG ---------------------
  result := bmp.SaveToGraphic(xifPNG);
  //-- освободили -------------------------------------------
  img.Free;
  bmp.Free;
end;

Исходник

Код кажется не таким уж маленьким. Но это все просто подготовительная работа. Плюс строка комментария перед каждым оператором.

Как видите, абсолютно не важно, какой TGraphic передан. На выходе все равно будет PNG. Если добавить параметр «тип изображения в результате» в функцию CreaCircleAva получаем конвертер из одного графического формата в другой.

Планы на будущее TxIPCanvas

Безусловно, пока все еще далеко до совершенства. Но я работаю над этим. Использую в своей повседневной работе.

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

GDI+ TCanvas призван облегчить написание, уменьшить код, использовать свои старые наработки. На текущий момент, пока еще требуется указывать модули GDIPAPI, GDIPOBJ, т.к. лично я часто использую такие прелести, как TGPGraphicsPath и иже с ним. Но, между тем, множество рутины уже спрятано под капот.

Для пока не учтенных возможностей в TxIPCanvas предусмотрено свойство GPGraphics : TGPGraphics. С этим свойством можно работать привычным для GDI+ способом. Также, каждое из свойств IPPen, IPBrush, IPFont, имеет в арсенале свойство, реализующее «родной» для GDI+ класс: GPPen:TGPPen, GPBrush: TGPBrush, GPFont : TGPFont.

В чем, собственно, проблема «упрятать» под капот. Дело вовсе не в технических сложностях, а в удобстве пользования. Любой метод должен заменить собой много строк кода с минимальными потерями в функциональности. Допустим, прекрасно понимаю, что на текущий момент, используя, скажем, возможность «мультиградиента», надо обязательно указывать GradRect перед вызовом прямых методов GPGraphics, иначе будет исключение. Хотя, у того же TGPGraphicsPath есть отличный метод GetBounds, который можно использовать при автоматизации всего этого действа. И конструкция

pth.GetBounds(gprct);
IPBrush.GradRect := gprct;
GPGraphics.FillPath(IPBrush.GPBrush,pth);
GPGraphics.DrawPath(IPPen.GPPen,pth);

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

Со временем, когда сформируется четкое видение, как тот или иной метод должен выглядеть, буду дописывать. В планах и подробное описание. Пока рекомендую подсматривать в примерах. Там все максимально прокомментировано.

Хотелось бы услышать, увидеть пожелания, здоровую критику и мысли по поводу пользы/вреда GDI+ TCanvas.

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *