Difference between revisions of "Developing with Graphics/ru"

From Free Pascal wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(77 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Developing with Graphics}}
 
{{Developing with Graphics}}
 
== Программирование графики ==
 
 
  
 
Эта страница описывает основные классы и технологии рисования графики в Lazarus. Какие-то специфические вещи ищите в других статьях.
 
Эта страница описывает основные классы и технологии рисования графики в Lazarus. Какие-то специфические вещи ищите в других статьях.
Line 14: Line 11:
 
==Другие статьи по графике==
 
==Другие статьи по графике==
 
===2D-рисование===
 
===2D-рисование===
* [[ZenGL]] - кроссплатформенная библиотека для разработки игр на основе OpenGL.
+
* [[ZenGL/ru|ZenGL]] - кроссплатформенная библиотека для разработки игр на основе OpenGL.
* [[BGRABitmap]] - Рисование фигур, прозрачных изображений, прямой доступ к пикселям и др.
+
* [[BGRABitmap/ru|BGRABitmap]] - Рисование фигур, прозрачных изображений, прямой доступ к пикселям и др.
 
* [[LazRGBGraphics]] - Пакет для быстрой обработки изображения в памяти и работы с пикселями (например, scan line).
 
* [[LazRGBGraphics]] - Пакет для быстрой обработки изображения в памяти и работы с пикселями (например, scan line).
 
* [[fpvectorial]] - Предоставляет возможность работы с векторной графикой.
 
* [[fpvectorial]] - Предоставляет возможность работы с векторной графикой.
* [[Double Gradient]] - Draw 'double gradient' & 'n gradient' bitmaps easy.
+
* [[Double Gradient]] - Рисуйте легко растровые изображения 'double gradient' и 'n-gradient'.
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.
+
* [[Gradient Filler]] - TGradientFiller - лучший способ создания пользовательских n-градиентов в Lazarus.
* [[PascalMagick]] - an easy to use API for interfacing with [http://www.imagemagick.org ImageMagick], a multiplatform free software suite to create, edit, and compose bitmap images.
+
* [[PascalMagick]] - простой в использовании API для взаимодействия с [http://www.imagemagick.org ImageMagick], многоплатформенным пакетом бесплатного программного обеспечения для создания, редактирования и создания растровых изображений.
* [[Sample Graphics]] -  graphics gallery created with Lazarus and drawing tools
+
* [[Sample Graphics]] -  графическая галерея, созданная с помощью Lazarus и инструментов для рисования.
* [[Fast direct pixel access]] - speed comparison of some methods for direct bitmap pixel access
+
* [[Fast direct pixel access]] - сравнение скорости некоторых методов для прямого доступа к растровому пикселю.
* [http://www.crossgl.com/aggpas/ AggPas] - AggPas is an Object Pascal native port of the Anti-Grain Geometry library. It is fast and very powerful with anti-aliased drawing and subpixel accuracy. You can think of AggPas as of a rendering engine that produces pixel images in memory from some vectorial data.
+
* [http://www.crossgl.com/aggpas/ AggPas] - это нативный порт Object Pascal библиотеки Anti-Grain Geometry. Он быстр и очень мощен [при работе] со сглаженным рисунком и субпиксельной точностью. Вы можете думать об AggPas как о механизме рендеринга, который создает пиксельные изображения в памяти из некоторых векторных данных.
  
 
===3D-рисование===
 
===3D-рисование===
* [[GLScene]] - A port of the 3D visual OpenGL graphics Library [http://www.glscene.org GLScene]
+
* [[GLScene]] - Порт 3D визуальной графической библиотеки [http://www.glscene.org GLScene OpenGL]
 +
 
 
===Диаграммы===
 
===Диаграммы===
 
* [[TAChart]] - компонента для рисования диаграмм в Lazarus
 
* [[TAChart]] - компонента для рисования диаграмм в Lazarus
Line 32: Line 30:
 
* [[Perlin Noise]] - статья об использовании Perlin Noise в LCL приложениях.
 
* [[Perlin Noise]] - статья об использовании Perlin Noise в LCL приложениях.
  
==Introduction to the Graphics model of the LCL==
+
==Введение в графическую модель LCL==
 +
 
 +
LCL предоставляет два вида классов рисования: нативные [(собственные)] классы и не-нативные [(сторонние)] классы. Нативные графические классы являются наиболее традиционным способом рисования графики в LCL, а также являются наиболее важными, в то время как не-нативные классы являются дополнительными, но также очень важными. Собственные классы в основном расположены в модуле '''Graphics''' LCL и являются хорошо известными классами: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic и т.д.
 +
 
 +
TCanvas - это класс, способный выполнять рисование. Он не может существовать один и должен быть либо прикреплен к чему-то видимому (или, по крайней мере, который может быть видимым), например, к визуальному элементу управления, происходящему из TControl, либо должен быть присоединен к внеэкранному буферу потомка от TRasterImage (TBitmap является наиболее часто использующимся). TFont, TBrush и TPen описывают, как рисование различных операций будет выполняться в Canvas.
  
The LCL provides two kinds of drawing class: Native classes and non-native classes. Native graphics classes are the most traditional way of drawing graphics in the LCL and are also the most important one, while the non-native classes are complementary, but also very important. The native classes are mostly located in the unit '''Graphics''' of the LCL and are the well known classes: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic, etc.
+
TRasterImage (обычно используется через его потомка TBitmap) - это область памяти, зарезервированная для рисования графики, но она создана для максимальной совместимости с собственным Canvas и, следовательно, в LCL-Gtk2 в X11 она расположена на сервере X11, который обеспечивает попиксельный доступ через свойство Pixels чрезвычайно медленно. В Windows это очень быстро, потому что Windows позволяет создавать локально выделенное изображение, которое может получать рисунки из Windows Canvas.
  
TCanvas is a class capable of executing drawings. It cannot exist alone and must either be attached to something visible (or at least which may possibly be visible), such as a visual control descending from TControl, or be attached to an off-screen buffer from a TRasterImage descendent (TBitmap is the most commonly used). TFont, TBrush and TPen describe how the drawing of various operations will be executed in the Canvas.
+
Помимо них существуют также не-нативные классы рисования, расположенные в графическом типе модулей (TRawImage), intfgraphics (TLazIntfImage) и lazcanvas (TLazCanvas, этот существует в Lazarus [с версии] 0.9.31+). TRawImage - это хранилище и описание области памяти, которая содержит изображение. TLazIntfImage - это изображение, которое присоединяется к TRawImage и обеспечивает преобразование между TFPColor и форматом настоящего пикселя TRawImage. TLazCanvas - это не нативный Canvas, который может рисовать изображение в TLazIntfImage.
  
TRasterImage (usually used via its descendant TBitmap) is a memory area reserved for drawing graphics, but it is created for maximum compatibility with the native Canvas and therefore in LCL-Gtk2 in X11 it is located in the X11 server, which makes pixel access via the Pixels property extremely slow. In Windows it is very fast because Windows allows creating a locally allocated image which can receive drawings from a Windows Canvas.  
+
Основное различие между нативными и не-нативными классами состоит в том, что нативные классы не выполняются одинаково на всех платформах, потому что рисование выполняется самой базовой платформой. Скорость, а также точный конечный результат рисования изображения могут иметь различия. Не-нативные классы гарантированно выполняют одинаковое рисование на всех платформах с точностью до пикселя, и все они работают достаточно быстро на всех платформах.
  
Besides these there are also non-native drawing classes located in the units graphtype (TRawImage), intfgraphics (TLazIntfImage) and lazcanvas (TLazCanvas, this one exists in Lazarus 0.9.31+). TRawImage is the storage and description of a memory area which contains an image. TLazIntfImage is an image which attaches itself to a TRawImage and takes care of converting between TFPColor and the real pixel format of the TRawImage. TLazCanvas is a non-native Canvas which can draw to an image in a TLazIntfImage.
+
В наборе виджетов LCL-CustomDrawn нативные классы реализованы с использованием не-нативных классов.
  
The main difference between the native classes and the non-native ones is that the native ones do not perform exactly the same in all platforms, because the drawing is done by the underlying platform itself. The speed and also the exact final result of the image drawing can have differences. The non-native classes are guaranteed to perform exactly the same drawing in all platforms with a pixel level precision and they all perform reasonably fast in all platforms.
+
Все эти классы будут лучше описаны в разделах ниже.
  
In the widgetset LCL-CustomDrawn the native classes are implemented using the non-native ones.
+
==Работа с TCanvas==
 +
=== Рисование прямоугольника ===
 +
Многие элементы управления, например TForm, TPanel или TPaintbox, отображают свой холст как общедоступное свойство или событие OnPaint. Давайте используем TForm в качестве примера, чтобы продемонстрировать, как рисовать на холсте.  
  
All of these classes will be better described in the sections below.
+
Предположим, мы хотим нарисовать красный прямоугольник с синей рамкой толщиной 5 пикселей в центре формы; размер прямоугольника должен составлять половину размера формы. Для этого мы должны добавить код в событие OnPaint формы. Не рисуйте в обработчике OnClick, потому что это рисование не является постоянным и будет стираться всякий раз, когда операционная система запрашивает перерисовку, всегда рисуйте в событии OnPaint!
  
==Working with TCanvas==
+
Метод TCanvas для рисования прямоугольника вызывается именно так: <tt>Rectangle()</tt>. Он получает координаты краев прямоугольника либо отдельно, либо в виде записи <tt>TRect</tt>. Цвет заливки определяется цветом кисти Холста, а цвет границы задается цветом пера холста:
  
===Using the default GUI font===
+
<syntaxhighlight lang=pascal>
 +
procedure TForm1.FormPaint(Sender: TObject);
 +
var
 +
  w, h: Integer;    // Ширина и высота прямоугольника
 +
  cx, cy: Integer;  // центр формы
 +
  R: TRect;        // запись, содержащая координаты левого, верхнего, правого, нижнего углов прямоугольника
 +
begin
 +
  // Высчитываем центр формы
 +
  cx := Width div 2;
 +
  cy := Height div 2;
  
This can be done with this simple code:
+
  // Рассчитываем размер прямоугольника
 +
  w := Width div 2;
 +
  h := Height div 2;
  
<syntaxhighlight>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight>
+
  // Рассчитываем углы прямоугольника
 +
  R.Left := cx - w div 2;
 +
  R.Top := cy - h div 2;
 +
  R.Right := cx + w div 2;
 +
  R.Bottom := cy + h div 2;
  
===Drawing a text limited on the width===
+
  // Устанавливаем цвет заливки
 +
  Canvas.Brush.Color := clRed;
 +
  Canvas.Brush.Style := bsSolid;
  
Use the DrawText routine, first with DT_CALCRECT and then without it.
+
  // Устанавливаем цвет границы
 +
  Canvas.Pen.Color := clBlue;
 +
  Canvas.Pen.Width := 5;
 +
  Canvas.Pen.Style := psSolid;
  
<syntaxhighlight>// First calculate the text size then draw it
+
  // Рисуем прямоугольник
 +
  Canvas.Rectangle(R);
 +
end;
 +
</syntaxhighlight>
 +
 
 +
===Использование шрифта GUI по умолчанию===
 +
 
 +
Это можно сделать с помощью этого простого кода:
 +
 
 +
<syntaxhighlight lang=pascal>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight>
 +
 
 +
===Рисование ограниченного по ширине текста===
 +
 
 +
Используйте процедуру DrawText, сначала с DT_CALCRECT, а затем без него.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
// Сначала рассчитываем размер текста, затем рисуем его
 
TextBox := Rect(0, currentPos.Y, Width, High(Integer));
 
TextBox := Rect(0, currentPos.Y, Width, High(Integer));
DrawText(ACanvas.Handle, PChar(Text), Length(Text),
+
DrawText(ACanvas.Handle, PChar(Text), Length(Text), TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);
  TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);
 
  
DrawText(ACanvas.Handle, PChar(Text), Length(Text),
+
DrawText(ACanvas.Handle, PChar(Text), Length(Text), TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight>
  TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight>
 
  
===Drawing text with sharp edges (non antialiased)===
+
===Рисование текста с резкими краями (без сглаживания)===
  
Some widgetsets support this via
+
Некоторые виджеты поддерживают это через
  
<syntaxhighlight>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight>
+
<syntaxhighlight lang=pascal>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight>
  
Some widgetsets like the gtk2 do not support this and always paint antialiased. Here is a simple procedure to draw text with sharp edges under gtk2. It does not consider all cases, but it should give an idea:
+
Некоторые виджеты, такие как gtk2, не поддерживают это и всегда рисуют сглаживание. Вот простая процедура рисования текста с резкими краями под gtk2. Она не предусматривает все случаи, но должна дать представление:
  
<syntaxhighlight>procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
+
<syntaxhighlight lang=pascal>
 +
procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
 
var
 
var
 
   w,h: integer;
 
   w,h: integer;
Line 94: Line 133:
 
   IntfImg:=nil;
 
   IntfImg:=nil;
 
   try
 
   try
     // paint text to a bitmap
+
     // рисуем текст в растровое изображение
 
     Img.Masked:=true;
 
     Img.Masked:=true;
 
     Img.SetSize(w,h);
 
     Img.SetSize(w,h);
Line 102: Line 141:
 
     Img.Canvas.Font:=Canvas.Font;
 
     Img.Canvas.Font:=Canvas.Font;
 
     Img.Canvas.TextOut(0,0,TheText);
 
     Img.Canvas.TextOut(0,0,TheText);
     // get memory image
+
     // получаем изображение в памяти
 
     IntfImg:=Img.CreateIntfImage;
 
     IntfImg:=Img.CreateIntfImage;
     // replace gray pixels
+
     // заменяем серые пиксели
 
     FontColor:=ColorToRGB(Canvas.Font.Color);
 
     FontColor:=ColorToRGB(Canvas.Font.Color);
 
     for dy:=0 to h-1 do begin
 
     for dy:=0 to h-1 do begin
Line 114: Line 153:
 
       end;
 
       end;
 
     end;
 
     end;
     // create bitmap
+
     // создаем растровое изображение
 
     Img.LoadFromIntfImage(IntfImg);
 
     Img.LoadFromIntfImage(IntfImg);
     // paint
+
     // рисуем
 
     Canvas.Draw(x,y,Img);
 
     Canvas.Draw(x,y,Img);
 
   finally
 
   finally
Line 124: Line 163:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
==Working with TBitmap and other TGraphic descendents==
+
==Работа с TBitmap и другими потомками TGraphic==
The TBitmap object stores a bitmap where you can draw before showing it to the screen. When you create a bitmap, you must specify the height and width, otherwise it will be zero and nothing will be drawn. And in general all other TRasterImage descendents provide the same capabilities. One should use the one which matches the format desired for output/input from the disk or TBitmap in case disk operations will not be performed as well as for the Windows Bitmap (*.bmp) format.
+
 
 +
Объект TBitmap хранит растровое изображение, где вы можете рисовать, прежде чем показывать его на экране. Когда вы создаете растровое изображение, вы должны указать высоту и ширину, иначе это будет ноль, и ничто не будет нарисовано. И вообще, все остальные потомки TRasterImage предоставляют те же возможности. Следует использовать тот, который соответствует формату, необходимому для вывода/ввода с диска или TBitmap, в случае, если операции с дисками не будут выполняться, а также для формата Windows Bitmap (*.bmp).
 +
 
 +
===Загрузка/Сохранение изображения из/на диск===
 +
Чтобы загрузить изображение с диска, используйте [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.loadfromfile.html TGraphic.LoadFromFile] и для сохранения его на другом диске, используйте [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.savetofile.html TGraphic.SaveToFile]. Используйте соответствующий потомок TGraphic, который соответствует ожидаемому формату. См. [[Developing_with_Graphics/ru#Форматы изображений|форматы изображений]] для [просмотра] списка доступных классов формата изображения.
  
===Loading/Saving an image from/to the disk===
+
<syntaxhighlight lang=pascal>
To load an image from the disk use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.loadfromfile.html TGraphic.LoadFromFile] and to save it to another disk file use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.savetofile.html TGraphic.SaveToFile]. Use the appropriate TGraphic descendent which matches the format expected. See [[Developing_with_Graphics#Image_formats]] for a list of available image format classes.
 
<syntaxhighlight>
 
 
var
 
var
 
   MyBitmap: TBitmap;
 
   MyBitmap: TBitmap;
Line 135: Line 176:
 
   MyBitmap := TBitmap.Create;
 
   MyBitmap := TBitmap.Create;
 
   try
 
   try
     // Load from disk
+
     // Загрузка с диска
 
     MyBitmap.LoadFromFile(MyEdit.Text);
 
     MyBitmap.LoadFromFile(MyEdit.Text);
  
     // Here you can use MyBitmap.Canvas to read/write to/from the image
+
     // Здесь вы можете использовать MyBitmap.Canvas для чтения/записи в/из изображения
  
     // Write back to another disk file
+
     // Записываем обратно на другой диск
 
     MyBitmap.SaveToFile(MyEdit2.Text);
 
     MyBitmap.SaveToFile(MyEdit2.Text);
 
   finally
 
   finally
Line 147: Line 188:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
When using any other format the process is compelely identical, just use the adequate class. For example, for PNG images:
+
При использовании любого другого формата процесс полностью идентичен, просто используйте соответствующий класс. Например, для изображений PNG:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   MyPNG: TPortableNetworkGraphic;
 
   MyPNG: TPortableNetworkGraphic;
Line 155: Line 196:
 
   MyPNG := TPortableNetworkGraphic.Create;
 
   MyPNG := TPortableNetworkGraphic.Create;
 
   try
 
   try
     // Load from disk
+
     // Загрузка с диска
 
     MyPNG.LoadFromFile(MyEdit.Text);
 
     MyPNG.LoadFromFile(MyEdit.Text);
  
     // Here you can use MyPNG.Canvas to read/write to/from the image
+
     // Здесь вы можете использовать MyPNG.Canvas для чтения/записи в/из изображения
  
     // Write back to another disk file
+
     // Записываем обратно на другой диск
 
     MyPNG.SaveToFile(MyEdit2.Text);
 
     MyPNG.SaveToFile(MyEdit2.Text);
 
   finally
 
   finally
Line 167: Line 208:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
If you don't know beforehand the format of the image, use TPicture which will determine the format based in the file extension. Note that TPicture does not support all formats supported by Lazarus, as of Lazarus 0.9.31 it supports BMP, PNG, JPEG, Pixmap and PNM while Lazarus also supports ICNS and other formats:
+
Если вы заранее не знаете формат изображения, используйте TPicture, который определит формат на основе расширения файла. Обратите внимание, что TPicture не поддерживает все форматы, поддерживаемые Lazarus, с Lazarus 0.9.31 он поддерживает BMP, PNG, JPEG, Pixmap и PNM, в то время как Lazarus также поддерживает ICNS и другие форматы:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   MyPicture: TPicture;
 
   MyPicture: TPicture;
Line 175: Line 216:
 
   MyPicture := TPicture.Create;
 
   MyPicture := TPicture.Create;
 
   try
 
   try
     // Load from disk
+
     // Загрузка с диска
 
     MyPicture.LoadFromFile(MyEdit.Text);
 
     MyPicture.LoadFromFile(MyEdit.Text);
  
     // Here you can use MyPicture.Graphic.Canvas to read/write to/from the image
+
     // Здесь вы можете использовать MyPicture.Graphic.Canvas для чтения/записи в/из изображения
  
     // Write back to another disk file
+
     // Записываем обратно на другой диск
 
     MyPicture.SaveToFile(MyEdit2.Text);
 
     MyPicture.SaveToFile(MyEdit2.Text);
 
   finally
 
   finally
Line 187: Line 228:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
=== Additional file formats for TImage ===
+
=== Дополнительные форматы файлов для TImage ===
You can add additional file format support by adding the [[fcl-image]] fpread* and/or fpwrite* units to your uses clause. In this way, you can e.g. add support for TIFF for TImage
+
Вы можете добавить дополнительную поддержку форматов файлов, добавив модули [[fcl-image]] fpread* и/или fpwrite* в вашу секцию uses. Таким образом, вы можете, например, добавить поддержку TIFF для TImage
  
===Direct pixel access===
+
===Прямой доступ к пикселям===
To do directly access the pixels of bitmaps one can either use external libraries, such as [[BGRABitmap]], [[LazRGBGraphics]] and [[Current conversion projects#Graphics32|Graphics32]] or use the Lazarus native TLazIntfImage. For a comparison of pixel access methods, see [[Fast direct pixel access|fast direct pixel access]].
+
Для непосредственного доступа к пикселям растровых изображений можно использовать внешние библиотеки, такие как [[BGRABitmap]], [[LazRGBGraphics]] и [[Current_conversion_projects/ru#Graphics32|Graphics32]], или использовать нативный Lazarus'овский TLazIntfImage. Сравнение методов доступа к пикселям см. [[Fast direct pixel access|fast direct pixel access]].
  
On some Lazarus widgetsets (notably LCL-Gtk2), the bitmap data is not stored in memory location which can be accessed by the application and in general the LCL native interfaces draw only through native Canvas routines, so each SetPixel / GetPixel operation involves a slow call to the native Canvas API. In LCL-CustomDrawn this is not the case since the bitmap is locally stored for all backends and SetPixel / GetPixel is fast. For obtaining a solution which works in all widgetsets one should use TLazIntfImage. As Lazarus is meant to be platform independent and work in gtk2, the TBitmap class does not provide a property like Scanline. There is a GetDataLineStart function, equivalent to Scanline, but only available for memory images like [[Developing with Graphics#Working with TLazIntfImage|TLazIntfImage]] which internally uses TRawImage.
+
В некоторых наборах виджетов Lazarus (в частности, LCL-Gtk2) данные растрового изображения не сохраняются в памяти, к которой может обращаться приложение, и в общем случае собственные интерфейсы LCL рисуются только через собственные процедуры Canvas, поэтому каждая операция SetPixel / GetPixel включает медленный вызов родного Canvas API. В LCL-CustomDrawn это не так, поскольку растровое изображение локально сохраняется для всех бэкэндов, а SetPixel / GetPixel работает быстро. Для получения решения, которое работает во всех наборах виджетов, следует использовать TLazIntfImage. Поскольку Lazarus должен быть независимым от платформы и работать в gtk2, класс TBitmap не предоставляет такое свойство, как Scanline. Существует функция GetDataLineStart, эквивалентная Scanline, но доступная только для образов памяти, таких как [[Developing_with_Graphics/ru#Работа с TLazIntfImage, TRawImage и TLazCanvas|TLazIntfImage]], которая внутренне использует TRawImage.
  
To sum it up, with the standard TBitmap, you can only change pixels indirectly, by using TCanvas.Pixels. Calling a native API to draw / read an individual pixel is course slower than direct pixel access, notably so in LCL-gtk2 and LCL-Carbon.
+
Подводя итог, можно сказать, что со стандартным TBitmap вы можете только косвенно изменять пиксели, используя TCanvas.Pixels. Вызов собственного API для рисования / чтения отдельного пикселя, конечно, медленнее, чем прямой доступ к пикселям, особенно в LCL-gtk2 и LCL-Carbon.
  
===Drawing color transparent bitmaps===
+
----
 +
[[User:Zoltanleo|Прим.перев.]]: Есть весьма [https://stackoverflow.com/questions/13583451/how-to-use-scanline-property-for-24-bit-bitmaps неплохая статья] (правда, на английском, чуть позже сделаю перевод и выложу на какой-нибудь ресурс), объясняющая суть работы свойства Scanline.
 +
----
  
A new feature, implemented on Lazarus 0.9.11, is color transparent bitmaps. Bitmap files (*.BMP) cannot store any information about transparency, but they can work as they had if you select a color on them to represent the transparent area. This is a common trick used on Win32 applications.
+
===Рисование растровых изображений прозрачным цветом===
  
The following example loads a bitmap from a Windows resource, selects a color to be transparent (clFuchsia) and then draws it to a canvas.
+
Новая функция, реализованная в Lazarus 0.9.11, - это растровые изображения с прозрачными цветами. В растровых файлах (*.BMP) не может храниться информация о прозрачности, но они могут работать так же, как если бы вы выбрали цвет для представления прозрачной области. Это распространенный прием, используемый в приложениях Win32.
  
<syntaxhighlight>procedure MyForm.MyButtonOnClick(Sender: TObject);
+
В следующем примере загружается растровое изображение из ресурса Windows, выбирается прозрачный цвет (clFuchsia) и затем он рисуется на холсте.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure MyForm.MyButtonOnClick(Sender: TObject);
 
var
 
var
 
   buffer: THandle;
 
   buffer: THandle;
Line 213: Line 259:
 
   buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));
 
   buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));
  
   if (buffer = 0) then exit; // Error loading the bitmap
+
   if (buffer = 0) then exit; // Ошибка загрузки растрового изображения
  
 
   bmp.Handle := buffer;
 
   bmp.Handle := buffer;
Line 230: Line 276:
 
   MyCanvas.Draw(0, 0, bmp);
 
   MyCanvas.Draw(0, 0, bmp);
  
   bmp.Free; // Release allocated resource
+
   bmp.Free; // Освобождаем выделенный ресурс
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
Notice the memory operations performed with the [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. They are necessary to ensure the correct loading of the image.
+
Обратите внимание на операции с памятью, выполненные с помощью [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. Они необходимы для обеспечения правильной загрузки изображения.
  
===Taking a screenshot of the screen===
+
===Получение скриншота экрана===
  
Since Lazarus 0.9.16 you can use LCL to take screenshots of the screen on a cross-platform way. The following example code does it (works on gtk2 and win32, but not gtk1 currently):
+
Начиная с Lazarus 0.9.16, вы можете использовать LCL, чтобы делать скриншоты экрана кросс-платформенным способом. Следующий пример кода делает это (работает на gtk2 и win32, но не на gtk1 в настоящее время):
 
 
<syntaxhighlight>uses Graphics, LCLIntf, LCLType;
 
  
 +
<syntaxhighlight lang=pascal>
 +
uses Graphics, LCLIntf, LCLType;
 
   ...
 
   ...
 
 
var
 
var
 
   MyBitmap: TBitmap;
 
   MyBitmap: TBitmap;
Line 251: Line 296:
 
   MyBitmap.LoadFromDevice(ScreenDC);
 
   MyBitmap.LoadFromDevice(ScreenDC);
 
   ReleaseDC(0,ScreenDC);
 
   ReleaseDC(0,ScreenDC);
 
 
   ...</syntaxhighlight>
 
   ...</syntaxhighlight>
  
==Working with TLazIntfImage, TRawImage and TLazCanvas==
+
==Работа с TLazIntfImage, TRawImage и TLazCanvas==
  
TLazIntfImage is a non-native equivalent of TRasterImage (more commonly utilized in the form of it's descendent TBitmap). The first thing to be aware about this class is that unlike TBitmap it will not automatically allocate a memory area for the bitmap, one should first initialize a memory area and then give it to the TLazIntfImage. Right after creating a TLazIntfImage one should either connect it to a TRawImage or load it from a TBitmap.
+
TLazIntfImage не является нативным эквивалентом TRasterImage (чаще используется в форме потомка TBitmap). Первое, что нужно знать об этом классе, это то, что в отличие от TBitmap, он не будет автоматически выделять область памяти для растрового изображения, сначала нужно инициализировать область памяти, а затем передать ее в TLazIntfImage. Сразу после создания TLazIntfImage нужно либо подключить его к TRawImage, либо загрузить из TBitmap.
  
TRawImage is of the type object and therefore does not need to be created nor freed. It can either allocate the image memory itself when one calls TRawImage.CreateData or one can pass a memory block allocated for examply by a 3rd party library such as the Windows API of the Cocoa Framework from Mac OS X and pass the information of the image in TRawImage.Description, TRawImage.Data and TRawImage.DataSize. Instead of attaching it to a RawImage one could also load it from a TBitmap which will copy the data from the TBitmap and won't be syncronized with it afterwards. The TLazCanvas cannot exist alone and must always be attached to a TLazIntfImage.
+
TRawImage относится к типу объект и поэтому не нуждается ни в создании, ни в освобождении. Он может либо выделить память для самого изображения при вызове TRawImage.CreateData, либо передать блок памяти, выделенный, например, сторонней библиотекой, такой как Windows API Cocoa Framework из Mac OS X, и передать информацию об изображении в TRawImage.Description, TRawImage.Data и TRawImage.DataSize. Вместо того, чтобы прикреплять его к RawImage, можно также загрузить его из TBitmap, который будет копировать данные из TBitmap и впоследствии не будет синхронизироваться с ним. TLazCanvas не может существовать один и всегда должен быть присоединен к TLazIntfImage.
  
The example below shows how to choose a format for the data and ask the TRawImage to create it for us and then we attach it to a TLazIntfImage and then attach a TLazCanvas to it:
+
В приведенном ниже примере показано, как выбрать формат для данных и попросить TRawImage создать его для нас, а затем мы присоединим его к TLazIntfImage и потом присоединим к нему TLazCanvas:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses graphtype, intfgraphics, lazcanvas;
 
uses graphtype, intfgraphics, lazcanvas;
  
Line 278: Line 322:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Initializing a TLazIntfImage===
+
===Инициализация TLazIntfImage===
  
One cannot simply create an instance of TLazIntfImage and start using it. It needs to add a storage to it. There are 3 ways to do this:
+
Нельзя просто создать экземпляр TLazIntfImage и начать его использовать. Нужно добавить к нему хранилище. Есть 3 способа сделать это:
  
1. Attach it to a TRawImage
+
1. Прикрепить его к TRawImage
  
2. Load it from a TBitmap. Note that it will copy the memory of the TBitmap so it won't remain connected to it.
+
2. Загрузить его из TBitmap. Обратите внимание, что он скопирует память TBitmap, чтобы [экземпляр TLazIntfImage] не оставался подключенным к нему:
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang=pascal>
 
   SrcIntfImg:=TLazIntfImage.Create(0,0);
 
   SrcIntfImg:=TLazIntfImage.Create(0,0);
 
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
 
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
3. Load it from a raw image description, like this:
+
3. Загрузить его из описания необработанного изображения, например так:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
   IntfImg := TLazIntfImage.Create(0,0);
 
   IntfImg := TLazIntfImage.Create(0,0);
 
   IntfImg.DataDescription:=GetDescriptionFromDevice(0);
 
   IntfImg.DataDescription:=GetDescriptionFromDevice(0);
Line 298: Line 343:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The 0 device in '''GetDescriptionFromDevice(0)''' uses the current screen format.
+
Устройство 0 в '''GetDescriptionFromDevice(0)''' использует текущий формат экрана.
  
 
===TLazIntfImage.LoadFromFile===
 
===TLazIntfImage.LoadFromFile===
  
Here is an example how to load an image directly into a TLazIntfImage. It initializes the TLazIntfImage to a 32bit RGBA format. Keep in mind that this is probably not the native format of your screen.
+
Вот пример того, как загрузить изображение непосредственно в TLazIntfImage. Он инициализирует TLazIntfImage в 32-битном формате RGBA. Имейте в виду, что это, вероятно, не собственный формат вашего экрана.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses LazLogger, Graphics, IntfGraphics, GraphType;
 
uses LazLogger, Graphics, IntfGraphics, GraphType;
 
procedure TForm1.FormCreate(Sender: TObject);
 
procedure TForm1.FormCreate(Sender: TObject);
Line 311: Line 356:
 
   lRawImage: TRawImage;
 
   lRawImage: TRawImage;
 
begin
 
begin
   // create a TLazIntfImage with 32 bits per pixel, alpha 8bit, red 8 bit, green 8bit, blue 8bit,
+
   // создаем TLazIntfImage с 32 битами на пиксель, альфа 8 бит, красный 8 бит, зеленый 8 бит, синий 8 бит,
  // Bits In Order: bit 0 is pixel 0, Top To Bottom: line 0 is top
+
  // порядок битов: бит 0 - это пиксель 0, сверху вниз: строка 0 - это верх
 
   lRawImage.Init;
 
   lRawImage.Init;
 
   lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);
 
   lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);
Line 319: Line 364:
 
   try
 
   try
 
     AImage.SetRawImage(lRawImage);
 
     AImage.SetRawImage(lRawImage);
     // Load an image from disk.
+
     // Загружаем изображение с диска.
     // It uses the file extension to select the right registered image reader.
+
     // Используется расширение файла для правильного выбора изображения зарегистрированным ридером.
     // The AImage will be resized to the width, height of the loaded image.
+
     // AImage будет масштабирован до  ширины и высоты загруженного изображения.
 
     AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');
 
     AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');
 
     debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);
 
     debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);
Line 330: Line 375:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Loading a TLazIntfImage into a TImage===
+
===Загрузка TLazIntfImage в TImage===
  
The pixel data of a '''TImage''' is the '''TImage.Picture''' property, which is of type ''TPicture''. '''TPicture''' is a multi format container containing one of several common image formats like Bitmap, Icon, Jpeg or PNG . Usually you will use the ''TPicture.Bitmap'' to load a '''TLazIntfImage''':
+
Пиксельные данные для '''TImage''' - это свойство '''TImage.Picture''', которое имеет тип ''TPicture''. '''TPicture''' - это мультиформатный контейнер, содержащий один из нескольких распространенных форматов изображений, таких как Bitmap, Icon, Jpeg или PNG. Обычно вы будете использовать ''TPicture.Bitmap'' для загрузки '''TLazIntfImage''':
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
     Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);
 
     Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''Notes:'''
+
'''Примечание:'''
*To load a '''transparent''' TLazIntfImage you have to set the '''Image1.Transparent''' to true.
+
*Чтобы загрузить '''прозрачный''' TLazIntfImage, вы должны установить для '''Image1.Transparent''' значение true.
*TImage uses the screen format. If the TLazIntfImage has a different format then the pixels will be converted. Hint: You can use '''IntfImg.DataDescription:=GetDescriptionFromDevice(0);''' to initialize the TLazIntfImage with the screen format.
+
*TImage использует формат экрана. Если TLazIntfImage имеет другой формат, то пиксели будут преобразованы.
  
===Fading example===
+
Подсказка: вы можете использовать '''IntfImg.DataDescription:=GetDescriptionFromDevice(0);''' для инициализации TLazIntfImage с форматом экрана.
  
A fading example with TLazIntfImage
+
===Пример обесцвечивания===
  
<syntaxhighlight>{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
+
Пример обесцвечивания с [использованием] TLazIntfImage
 +
 
 +
<syntaxhighlight lang=pascal>
 +
{ Этот код был взят из $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
 
uses LCLType, // HBitmap type
 
uses LCLType, // HBitmap type
 
     IntfGraphics, // TLazIntfImage type
 
     IntfGraphics, // TLazIntfImage type
Line 385: Line 433:
 
  end;</syntaxhighlight>
 
  end;</syntaxhighlight>
  
===Image format specific example===
+
===Пример конкретного формата изображения===
  
If you know that the TBitmap is using blue 8bit, green 8bit, red 8bit you can directly access the bytes, which is somewhat faster:
+
Если вы знаете, что TBitmap использует синий 8-битный, зеленый 8-битный, красный 8-битный [каналы], вы можете напрямую получить доступ к байтам, что несколько быстрее:
  
<syntaxhighlight>uses LCLType, // HBitmap type
+
<syntaxhighlight lang=pascal>
 +
uses LCLType, // HBitmap type
 
     IntfGraphics, // TLazIntfImage type
 
     IntfGraphics, // TLazIntfImage type
 
     fpImage; // TFPColor type
 
     fpImage; // TFPColor type
Line 415: Line 464:
 
   TempBitmap:=TBitmap.Create;
 
   TempBitmap:=TBitmap.Create;
 
    
 
    
   //with Scanline-like
+
   //со Scanline-подобным [свойством]
 
   for FadeStep:=1 to 32 do begin
 
   for FadeStep:=1 to 32 do begin
 
     for py:=0 to IntfImg1.Height-1 do begin
 
     for py:=0 to IntfImg1.Height-1 do begin
       Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
+
       Row1 := IntfImg1.GetDataLineStart(py); //как Delphi TBitMap.ScanLine
       Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine
+
       Row2 := IntfImg2.GetDataLineStart(py); //как Delphi TBitMap.ScanLine
 
       for px:=0 to IntfImg1.Width-1 do begin
 
       for px:=0 to IntfImg1.Width-1 do begin
 
         Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
 
         Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
         Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading
+
         Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Исчезновение
 
         Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
 
         Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
 
       end;
 
       end;
Line 438: Line 487:
 
  end;</syntaxhighlight>
 
  end;</syntaxhighlight>
  
===Conversion between TLazIntfImage and TBitmap===
+
===Преобразование между TLazIntfImage и TBitmap===
  
Since Lazarus has no TBitmap.ScanLines property, the best way to access the pixels of an image in a fast way for both reading and writing is by using TLazIntfImage. The TBitmap can be converted to a TLazIntfImage by using TBitmap.CreateIntfImage() and after modifying the pixels it can be converted back to a TBitmap by using TBitmap.LoadFromIntfImage();
+
Поскольку Lazarus не имеет свойства TBitmap.ScanLines, лучший способ быстрого доступа к пикселям изображения для чтения и записи - использование TLazIntfImage. TBitmap можно преобразовать в TLazIntfImage с помощью TBitmap.CreateIntfImage (), а после изменения пикселей он может быть преобразован обратно в TBitmap с помощью TBitmap.LoadFromIntfImage ();
Here's the sample on how to create TLazIntfImage from TBitmap, modify it and then go back to the TBitmap.
+
Вот пример того, как создать TLazIntfImage из TBitmap, изменить его и затем вернуться к TBitmap.
  
<syntaxhighlight>uses
+
<syntaxhighlight lang=pascal>
 +
uses
 
   ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...
 
   ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...
  
Line 456: Line 506:
 
     t := b.CreateIntfImage;
 
     t := b.CreateIntfImage;
  
     // Read and/or write to the pixels
+
     // Читаем и/или записываем в пиксели
 
     t.Colors[10,20] := colGreen;
 
     t.Colors[10,20] := colGreen;
  
Line 466: Line 516:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
===Using the non-native StretchDraw from LazCanvas===
+
===Использование не-нативного StretchDraw из LazCanvas===
  
Just like TCanvas.StretchDraw there is TLazCanvas.StretchDraw but you need to specify the interpolation which you desire to use. The interpolation which provides a Windows-like StretchDraw with a very sharp result (the opposite of anti-aliased) can be added with: TLazCanvas.Interpolation := TFPSharpInterpolation.Create;
+
Как и в случае с TCanvas.StretchDraw, существует TLazCanvas.StretchDraw, но вам нужно указать интерполяцию, которую вы хотите использовать. Интерполяция, которая обеспечивает Windows-подобный StretchDraw с очень резким результатом (противоположным сглаживанию), может быть добавлена с помощью: <tt>TLazCanvas.Interpolation: = TFPSharpInterpolation.Create;</tt>
  
There are other interpolations available in the unit fpcanvas.
+
В модуле fpcanvas доступны другие [способы] интерполяции.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses intfgraphics, lazcanvas;
 
uses intfgraphics, lazcanvas;
  
Line 480: Line 530:
 
   DestCanvas: TLazCanvas;
 
   DestCanvas: TLazCanvas;
 
begin
 
begin
   // Prepare the destination
+
   // Подготовка получателя
  
 
   DestIntfImage := TLazIntfImage.Create(0, 0);
 
   DestIntfImage := TLazIntfImage.Create(0, 0);
Line 487: Line 537:
 
   DestCanvas := TLazCanvas.Create(DestIntfImage);
 
   DestCanvas := TLazCanvas.Create(DestIntfImage);
  
   //Prepare the source
+
   //Подготовка источника
 
   SourceIntfImage := TLazIntfImage.Create(0, 0);
 
   SourceIntfImage := TLazIntfImage.Create(0, 0);
 
   SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);
 
   SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);
  
   // Execute the stretch draw via TFPSharpInterpolation
+
   // Выполнение процесса растяжения [рисунка] с помощью TFPSharpInterpolation
 
   DestCanvas.Interpolation := TFPSharpInterpolation.Create;
 
   DestCanvas.Interpolation := TFPSharpInterpolation.Create;
 
   DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);
 
   DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);
  
   // Reload the image into the TBitmap
+
   // Перезагрузка изображения в TBitmap
 
   DestBitmap.LoadFromIntfImage(DestIntfImage);
 
   DestBitmap.LoadFromIntfImage(DestIntfImage);
  
Line 508: Line 558:
 
   Bmp, DestBitmap: TBitmap;
 
   Bmp, DestBitmap: TBitmap;
 
begin
 
begin
   // Prepare the destination
+
   // Подготовка получателя
 
   DestBitmap := TBitmap.Create;
 
   DestBitmap := TBitmap.Create;
 
   DestBitmap.Width := 100;
 
   DestBitmap.Width := 100;
Line 525: Line 575:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Motion Graphics - How to Avoid flickering==
+
==Графика движения - как избежать мерцания==
  
Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when users sometimes sees the whole images and sometimes only when it is partially drawn. It occurs because the painting process requires time.
+
Многие программы выводят свои данные в графический интерфейс в виде 2D-графики. Если эту графику нужно быстро менять, вы скоро столкнетесь с проблемой: быстро меняющаяся графика часто мерцает на экране. Это происходит, когда пользователи иногда видят целые изображения, а иногда только когда они частично прорисованы. Это происходит потому, что процесс отрисовки требует времени.
  
But how can I avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. This tutorial will focus on drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, which provides a double-buffered canvas and a sprite component.
+
Но как я могу избежать мерцания и получить лучшую скорость рисования? Конечно, вы можете работать с аппаратным ускорением, используя OpenGL, но этот подход довольно тяжел для небольших программ или старых компьютеров. Этот урок будет сосредоточен на рисовании в TCanvas. Если вам нужна помощь с OpenGL, взгляните на пример, который поставляется с Lazarus. Вы также можете использовать игровой пакет A.J. Venter'а, который предоставляет холст с двойной буферизацией и спрайт-компонент.
  
A brief and very helpful article on avoiding flicker can be found at http://delphi.about.com/library/bluc/text/uc052102g.htm. Although written for Delphi, the techniques work well with Lazarus.
+
Краткую и очень полезную статью по предотвращению мерцания можно найти [http://delphi.about.com/library/bluc/text/uc052102g.htm здесь]. Хотя эти методы написаны для Delphi, они хорошо работают [и] с Lazarus.
  
Now we will examine the options we have for drawing to a Canvas:
+
Теперь мы рассмотрим варианты рисования на холсте:
* [[#Draw to a TImage|Draw to a TImage]]
+
* [[Developing_with_Graphics/ru#Рисование на TImage|Рисование на TImage]]
* [[#Draw on the OnPaint event|Draw on the OnPaint event of the form, a TPaintBox or another control]]
+
* [[Developing_with_Graphics/ru#Рисование в событии OnPaint|Рисование в событии OnPaint формы, TPaintBox или другого элемента управления]]
* [[#Create a custom control which draws itself|Create a custom control which draws itself]]
+
* [[Developing_with_Graphics/ru#Создание пользовательского элемента управления с самостоятельной отрисовкой|Создание пользовательского элемента управления с самостоятельной отрисовкой]]
* [[#Using A.J. Venter's gamepack|Using A.J. Venter's gamepack]]
 
  
===Draw to a TImage===
+
===Рисование на TImage===
  
A TImage consists of 2 parts: A TGraphic, usually a TBitmap, holding the persistent picture and the visual area, which is repainted on every OnPaint. Resizing the TImage does '''not''' resize the bitmap.
+
TImage состоит из 2 частей: TGraphic, обычно TBitmap, в котором хранится постоянное изображение и визуальная область, которые перекрашиваются на каждом OnPaint. Изменение размера TImage '''не''' изменяет размер растрового изображения.
The graphic (or bitmap) is accessible via Image1.Picture.Graphic (or Image1.Picture.Bitmap). The canvas is Image1.Picture.Bitmap.Canvas.  
+
Графическое изображение (или растровое изображение) доступно через Image1.Picture.Graphic (или Image1.Picture.Bitmap). Холст - это Image1.Picture.Bitmap.Canvas.
The canvas of the visual area of a TImage is only accessible during Image1.OnPaint via Image1.Canvas.
+
Холст визуальной области TImage доступен только во время [наступления события] Image1.OnPaint через Image1.Canvas.
  
'''Important''': Never use the OnPaint of the Image1 event to draw to the graphic/bitmap of a TImage. The graphic of a TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.
+
'''Важно''': Никогда не используйте событие OnPaint [компонента] Image1 для рисования в графическом/растровом изображении TImage. Графическое изображение TImage буферизуется, поэтому все, что вам нужно сделать, это нарисовать его из любого места, и изменения будут там навсегда. Однако, если вы постоянно перерисовываете, изображение будет мерцать. В этом случае вы можете попробовать другие варианты. Рисование в TImage считается медленнее, чем другие подходы.
  
====Resizing the bitmap of a TImage====
+
====Изменение размера растрового изображения TImage====
  
{{Note| Do not use this during OnPaint.}}
+
{{Note| Не используйте это в событии OnPaint.}}
  
<syntaxhighlight>with Image1.Picture.Bitmap do begin
+
<syntaxhighlight lang=pascal>
 +
with Image1.Picture.Bitmap do begin
 
   Width:=100;
 
   Width:=100;
 
   Height:=120;
 
   Height:=120;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
Same in one step:
+
То же самое в один присест:
  
<syntaxhighlight>with Image1.Picture.Bitmap do begin
+
<syntaxhighlight lang=pascal>
 +
with Image1.Picture.Bitmap do begin
 
   SetSize(100, 120);
 
   SetSize(100, 120);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
====Painting on the bitmap of a TImage====
+
====Рисование на растровом изображении TImage====
  
{{Note| Do not use this during OnPaint.}}
+
{{Note| Не используйте это в событии OnPaint.}}
  
<syntaxhighlight>with Image1.Picture.Bitmap.Canvas do begin
+
<syntaxhighlight lang=pascal>
   // fill the entire bitmap with red
+
with Image1.Picture.Bitmap.Canvas do begin
 +
   // заполняем все растровое изображение красным
 
   Brush.Color := clRed;
 
   Brush.Color := clRed;
 
   FillRect(0, 0, Width, Height);
 
   FillRect(0, 0, Width, Height);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
{{Note| Inside of Image1.OnPaint the Image1.Canvas points to the volatile visible area. Outside of Image1.OnPaint the Image1.Canvas points to Image1.Picture.Bitmap.Canvas.}}
+
{{Note| Внутри Image1.OnPaintImage1.Canvas указывает на изменчивую видимую область. За пределами Image1.OnPaint'а объект Image1.Canvas указывает на Image1.Picture.Bitmap.Canvas.}}
  
Another example:
+
Другой пример:
  
<syntaxhighlight>procedure TForm1.BitBtn1Click(Sender: TObject);
+
<syntaxhighlight lang=pascal>
 +
procedure TForm1.BitBtn1Click(Sender: TObject);
 
var
 
var
 
   x, y: Integer;
 
   x, y: Integer;
 
begin
 
begin
   // Draws the backgroung
+
   // Рисуем фон
 
   MyImage.Canvas.Pen.Color := clWhite;
 
   MyImage.Canvas.Pen.Color := clWhite;
 
   MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
 
   MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
 
    
 
    
   // Draws squares
+
   // Рисуем квадраты
 
   MyImage.Canvas.Pen.Color := clBlack;
 
   MyImage.Canvas.Pen.Color := clBlack;
 
   for x := 1 to 8 do
 
   for x := 1 to 8 do
Line 592: Line 645:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
==== Painting on the volatile visual area of the TImage====
+
====Рисование на изменчивой визуальной области TImage====
  
You can only paint on this area during OnPaint. OnPaint is eventually called automatically by the LCL when the area was invalidated. You can invalidate the area manually with Image1.Invalidate. This will not immediately call OnPaint and you can call Invalidate as many times as you want.
+
Вы можете рисовать только в этой области в событии OnPaint. [Событие] OnPaint в конечном итоге автоматически вызывается [библиотекой] LCL, когда область перерисовывается. Вы можете перерисовать область вручную с помощью Image1.Invalidate. Это вызовет OnPaint не сразу, и вы можете вызывать Invalidate столько раз, сколько захотите.
  
<syntaxhighlight>procedure TForm.Image1Paint(Sender: TObject);
+
<syntaxhighlight lang=pascal>
 +
procedure TForm.Image1Paint(Sender: TObject);
 
begin
 
begin
   // paint a line
+
   // рисуем линию
 
   Canvas.Pen.Color := clRed;
 
   Canvas.Pen.Color := clRed;
 
   Canvas.Line(0, 0, Width, Height);
 
   Canvas.Line(0, 0, Width, Height);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
===Draw on the OnPaint event===
+
===Рисование в событии OnPaint===
  
In this case all the drawing has to be done on the OnPaint event of the form, or of another control. The drawing isn't buffered like in the TImage, and it needs to be fully redrawn in each call of the OnPaint event handler.
+
В этом случае все рисование должно выполняться по событию OnPaint формы или другого элемента управления. Рисунок не буферизуется, как в TImage, и его необходимо полностью перерисовывать при каждом вызове обработчика события OnPaint.
  
<syntaxhighlight>procedure TForm.Form1Paint(Sender: TObject);
+
<syntaxhighlight lang=pascal>
 +
procedure TForm.Form1Paint(Sender: TObject);
 
begin
 
begin
   // paint a line
+
   // рисуем линию
 
   Canvas.Pen.Color := clRed;
 
   Canvas.Pen.Color := clRed;
 
   Canvas.Line(0, 0, Width, Height);
 
   Canvas.Line(0, 0, Width, Height);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
===Create a custom control which draws itself===
+
===Создание пользовательского элемента управления с самостоятельной отрисовкой===
Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case there is no need to use the OnPaint event of the control.
+
Создание пользовательского элемента управления имеет преимущество структурирования вашего кода, и вы можете использовать его повторно. Этот подход очень быстрый, но он все равно может вызывать мерцание, если вы сначала не рисуете на TBitmap'е, а лишь затем [начнете] рисовать на холсте. В этом случае нет необходимости использовать событие OnPaint элемента управления.
  
Here is an example custom control:
+
Вот пример пользовательского элемента управления:
  
<syntaxhighlight>uses
+
<syntaxhighlight lang=pascal>
 +
uses
 
   Classes, SysUtils, Controls, Graphics, LCLType;
 
   Classes, SysUtils, Controls, Graphics, LCLType;
 
   
 
   
Line 633: Line 689:
 
procedure TMyDrawingControl.EraseBackground(DC: HDC);
 
procedure TMyDrawingControl.EraseBackground(DC: HDC);
 
begin
 
begin
   // Uncomment this to enable default background erasing
+
   // Раскомментируйте это, чтобы включить удаление фона по умолчанию
 
   //inherited EraseBackground(DC);
 
   //inherited EraseBackground(DC);
 
end;  
 
end;  
Line 644: Line 700:
 
   Bitmap := TBitmap.Create;
 
   Bitmap := TBitmap.Create;
 
   try
 
   try
     // Initializes the Bitmap Size
+
     // Инициализируем размер растрового изображения
 
     Bitmap.Height := Height;
 
     Bitmap.Height := Height;
 
     Bitmap.Width := Width;
 
     Bitmap.Width := Width;
 
   
 
   
     // Draws the background
+
     // Рисуем фон
 
     Bitmap.Canvas.Pen.Color := clWhite;
 
     Bitmap.Canvas.Pen.Color := clWhite;
 
     Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
     Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
   
 
   
     // Draws squares
+
     // Рисует квадраты
 
     Bitmap.Canvas.Pen.Color := clBlack;
 
     Bitmap.Canvas.Pen.Color := clBlack;
 
     for x := 1 to 8 do
 
     for x := 1 to 8 do
Line 667: Line 723:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
and how we create it on the form:
+
и как мы создаем это на форме:
<syntaxhighlight>procedure TMyForm.FormCreate(Sender: TObject);
+
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TMyForm.FormCreate(Sender: TObject);
 
begin
 
begin
 
   MyDrawingControl := TMyDrawingControl.Create(Self);
 
   MyDrawingControl := TMyDrawingControl.Create(Self);
Line 679: Line 737:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
It is destroyed automatically, because we use Self as owner.
+
Он уничтожается автоматически, потому что мы используем Self в качестве владельца.
  
Setting Top and Left to zero is not necessary, since this is the standard position, but is done so to reinforce where the control will be put.
+
Установка Top и Left на ноль не обязательна, так как это стандартная позиция, но это делается для того, чтобы усилить то место, где будет помещен элемент управления.
  
"MyDrawingControl.Parent := Self;" is very important and you won't see your control if you don't do so.
+
"<tt>MyDrawingControl.Parent := Self;</tt>" - очень важная [строка кода], и вы не увидите свой элемент управления, если вы этого не сделаете.
  
"MyDrawingControl.DoubleBuffered := True;" is required to avoid flickering on Windows. It has no effect on gtk.
+
"<tt>MyDrawingControl.DoubleBuffered := True;</tt>" требуется, чтобы избежать мерцания в Windows. Это не [оказывает никакого] влияения на GTK.
  
== Image formats ==
+
== Форматы изображений ==
  
Here is a table with the correct class to use for each image format.
+
Вот таблица с правильным классом для каждого формата изображения.
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 714: Line 772:
 
|}
 
|}
  
See also the list of [[fcl-image#Image_formats|fcl-image supported formats]].
+
См. также список [[fcl-image#Image_formats|fcl-image supported formats]].
  
=== Converting formats ===
+
=== Преобразование форматов ===
Sometimes it must be necessary to convert one graphic type to another.
+
Иногда необходимо преобразовать один графический тип в другой.
One of the ways is to convert a graphic to intermediate format, and then convert it to TBitmap.
+
Одним из способов является преобразование графического объекта в промежуточный формат, а затем преобразование его в TBitmap.
Most of the formats can create an image from TBitmap.
+
Большинство форматов могут создавать изображения из TBitmap.
  
Converting Bitmap to PNG and saving it to a file:
+
Преобразование растрового изображения в PNG и сохранение его в файл:
  
<syntaxhighlight>procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
+
<syntaxhighlight lang=pascal>
 +
procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
 
var
 
var
 
   png : TPortableNetworkGraphic;  
 
   png : TPortableNetworkGraphic;  
Line 736: Line 795:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
==Pixel Formats==
+
==Пиксельные форматы==
  
 
===TColor===
 
===TColor===
  
The internal pixel format for TColor in the LCL is the XXBBGGRR format, which matches the native Windows format and is opposite to most other libraries, which use AARRGGBB. The XX part is used to identify if the color is a fixed color, which case XX should be 00 or if it is an index to a system color. There is no space reserved for an alpha channel.
+
Внутренний пиксельный формат для TColor в LCL - это формат XXBBGGRR, который соответствует собственному формату Windows и противоположен большинству других библиотек, которые используют AARRGGBB. Часть XX используется для определения того, является ли цвет фиксированным цветом, в этом случае XX должен быть 00 или это индекс системного цвета. Нет места для альфа-канала.
  
To convert from separate RGB channels to TColor use:
+
Для преобразования из отдельных каналов RGB в TColor используйте:
  
<syntaxhighlight>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight>
+
<syntaxhighlight lang=pascal>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight>
  
To get each channel of a TColor variable use the Red, Green and Blue functions:
+
Чтобы получить каждый канал переменной TColor, используйте функции Red, Green и Blue:
  
<syntaxhighlight>RedVal := Red(MyColor);
+
<syntaxhighlight lang=pascal>
 +
RedVal := Red(MyColor);
 
GreenVal := Green(MyColor);
 
GreenVal := Green(MyColor);
 
BlueVal := Blue(MyColor);</syntaxhighlight>
 
BlueVal := Blue(MyColor);</syntaxhighlight>
Line 754: Line 814:
 
===TFPColor===
 
===TFPColor===
  
TFPColor uses the AARRGGBB format common to most libraries, but it uses 16-bits for the depth of each color channel, totaling 64-bits per pixel, which is unusual. This does not necessarily mean that images will consume that much memory, however. Images created using TRawImage+TLazIntfImage can have any internal storage format and then on drawing operations TFPColor is converted to this internal format.
+
TFPColor использует формат AARRGGBB, общий для большинства библиотек, но он использует 16-битную глубину каждого цветового канала, что составляет 64 бита на пиксель, что является необычным. Однако это не обязательно означает, что изображения будут занимать столько памяти. Изображения, созданные с помощью TRawImage + TLazIntfImage, могут иметь любой внутренний формат хранения, а затем при операциях рисования TFPColor преобразуется в этот внутренний формат.
  
The unit Graphics provides routines to convert between TColor and TFPColor:
+
Модуль Graphics предоставляет процедуры для преобразования между TColor и TFPColor:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;
 
function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;
 
function FPColorToTColor(const FPColor: TFPColor): TColor;
 
function FPColorToTColor(const FPColor: TFPColor): TColor;
 
function TColorToFPColor(const c: TColorRef): TFPColor; overload;
 
function TColorToFPColor(const c: TColorRef): TFPColor; overload;
function TColorToFPColor(const c: TColor): TFPColor; overload; // does not work on system color
+
function TColorToFPColor(const c: TColor): TFPColor; overload; // не работает для системного цвета
 +
</syntaxhighlight>
 +
 
 +
==Рисование с помощью fcl-image==
 +
 
 +
Вы можете рисовать изображения, которые не будут отображаться на экране без LCL, просто используя fcl-image напрямую. Например, программа, работающая на веб-сервере без X11, может выиграть от отсутствия визуальной библиотеки в качестве зависимости. FPImage (псевдоним fcl-image) - это очень общая библиотека изображений и графики, полностью написанная на паскале. Фактически, LCL использует FPImage также для всех загрузок и сохранений из/в файлы и реализует функцию рисования через вызовы наборов виджетов (winapi, gtk, carbon, ...). Fcl-изображение с другой стороны также имеет процедуры рисования.
 +
 
 +
Для получения дополнительной информации, пожалуйста, прочитайте статью о [[fcl-image]].
 +
 
 +
==Распространенная ошибка [при использовании] OnPaint==
 +
 
 +
Распространенной ошибкой, которая вызывает много ложных сообщений об багах, является вызов события Onpaint для одного объекта из другого объекта. При использовании LCL это может работать в GTK2 и Windows, но, вероятно, не удастся с Qt, Carbon и Cocoa. Обычно не нужно вызывать <tt>Invalidate</tt>. Тем не менее, иногда это может быть необходимо в процедуре Button1Click,
 +
 
 +
вот так - плохо:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  Shape1Paint(Self); // Вызов события Shape1Onpaint
 +
  Shape1.Invalidate; // Вызываем фактическую перерисовку
 +
 
 +
  ... больше кода для Button1 ... 
 +
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Drawing with fcl-image==
+
Вот так - хорошо:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  ... код для Button1 ...
 +
  <Устанавливаем некоторые условия>;
 +
  // Shape1.Invalidate; // Может быть необходимо в некоторых случаях
 +
end;
 +
 
 +
// Shape1Paint должен быть прописан в  событии OnPaint объекта shape!
 +
procedure TForm1.Shape1Paint(Sender: TObject);
 +
var
 +
  Myrect: TRect;
 +
begin 
 +
  if <некоторое условие> then
 +
    with Shape1.Canvas do
 +
    begin
 +
      ... куча кода ...
 +
    end;
 +
end;   
 +
</syntaxhighlight>
  
You can draw images which won't be displayed in the screen without the LCL, by just using fcl-image directly. For example a program running on a webserver without X11 could benefit from not having a visual library as a dependency. FPImage (alias fcl-image) is a very generic image and drawing library written completely in pascal. In fact the LCL uses FPImage too for all the loading and saving from/to files and implements the drawing function through calls to the widgetset (winapi, gtk, carbon, ...). Fcl-image on the other hand also has drawing routines.
+
==Несколько полезных примеров==
 +
===Пример 1: Рисование на загруженном JPEG с [помощью] TImage===
 +
Добавьте процедуру LoadAndDraw в секцию public вашей формы и вставьте следующий код в раздел implementation:
  
For more information, please read the article about [[fcl-image]].
+
<syntaxhighlight lang=pascal>
 +
procedure TForm1.LoadAndDraw(const sFileName: String);
 +
var
 +
  jpg: TPicture;
 +
begin
 +
  jpg := TPicture.Create;
 +
  try
 +
    jpg.LoadFromFile(sFileName);
 +
 +
    Image.Picture.Bitmap.SetSize(jpg.Width, jpg.Height);
 +
    Image.Picture.Bitmap.Canvas.Draw(0, 0, jpg.Bitmap);
 +
 +
    Image.Picture.Bitmap.Canvas.Pen.Color := clRed;
 +
    Image.Picture.Bitmap.Canvas.Line(0, 0, 140, 140);
 +
  finally
 +
    jpg.Free;
 +
  end;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
===Пример 2: Рисование на элементах управления формы===
 +
 
 +
1) Создайте новый проект New project -> Application, добавьте в раздел uses следующие модули, если необходимо: Types, Controls, Graphics.
 +
 
 +
2) Положите на форму Button1, GroupBox1 и RadioGroup1
 +
 
 +
3) Положите на GroupBox1 еще одну кнопку - Button2
 +
 
 +
4) Ваша TForm1.Create должна выглядеть так:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.FormCreate(Sender: TObject);
 +
var
 +
  i: Integer;
 +
begin
 +
  for i := 0 to Self.ControlCount - 1 do
 +
    RadioGroup1.Items.AddObject(Controls[i].Name, Controls[i]);
 +
 
 +
  RadioGroup1.Items.AddObject(Button2.Name,Button2);
 +
end;
 +
</syntaxhighlight>
 +
 
 +
5) Для RadioGroup1 создайте обработчик события OnSelectionChanged:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.RadioGroup1SelectionChanged(Sender: TObject);
 +
begin
 +
  Self.Repaint;
 +
end; 
 +
</syntaxhighlight>
 +
 
 +
6) Добавьте в секцию public вашей формы процедуру HighlightControl:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.HighlightControl(AControl: TControl);
 +
var
 +
  R: Types.TRect;
 +
  aCC: TControlCanvas;
 +
begin
 +
  R := AControl.BoundsRect;
 +
  InflateRect(R, 2, 2);          // сделайте прямоугольник немного больше, чем элемент управления
 +
  aCC := TControlCanvas.Create;
 +
  aCC.Control := AControl.Parent;
 +
  aCC.Pen.Color := clGreen;
 +
  aCC.Pen.Width := 5;
 +
  aCC.Pen.Style := psSolid;
 +
  aCC.Brush.Style := bsClear;
 +
  aCC.Rectangle(R);
 +
  aCC.free;
 +
end;
 +
</syntaxhighlight>
  
[[Category:Tutorials/ru]]
+
==См. также==
{{AutoCategory}}
+
* [[Fast direct pixel access]]

Latest revision as of 07:03, 13 February 2020

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) 한국어 (ko) Nederlands (nl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

Эта страница описывает основные классы и технологии рисования графики в Lazarus. Какие-то специфические вещи ищите в других статьях.

Графические библиотеки

Графические библиотеки - здесь вы можете посмотреть, какие есть основные графические библиотеки.

Другие статьи по графике

2D-рисование

  • ZenGL - кроссплатформенная библиотека для разработки игр на основе OpenGL.
  • BGRABitmap - Рисование фигур, прозрачных изображений, прямой доступ к пикселям и др.
  • LazRGBGraphics - Пакет для быстрой обработки изображения в памяти и работы с пикселями (например, scan line).
  • fpvectorial - Предоставляет возможность работы с векторной графикой.
  • Double Gradient - Рисуйте легко растровые изображения 'double gradient' и 'n-gradient'.
  • Gradient Filler - TGradientFiller - лучший способ создания пользовательских n-градиентов в Lazarus.
  • PascalMagick - простой в использовании API для взаимодействия с ImageMagick, многоплатформенным пакетом бесплатного программного обеспечения для создания, редактирования и создания растровых изображений.
  • Sample Graphics - графическая галерея, созданная с помощью Lazarus и инструментов для рисования.
  • Fast direct pixel access - сравнение скорости некоторых методов для прямого доступа к растровому пикселю.
  • AggPas - это нативный порт Object Pascal библиотеки Anti-Grain Geometry. Он быстр и очень мощен [при работе] со сглаженным рисунком и субпиксельной точностью. Вы можете думать об AggPas как о механизме рендеринга, который создает пиксельные изображения в памяти из некоторых векторных данных.

3D-рисование

Диаграммы

  • TAChart - компонента для рисования диаграмм в Lazarus
  • PlotPanel - Создание и черчение анимированных графиков
  • Perlin Noise - статья об использовании Perlin Noise в LCL приложениях.

Введение в графическую модель LCL

LCL предоставляет два вида классов рисования: нативные [(собственные)] классы и не-нативные [(сторонние)] классы. Нативные графические классы являются наиболее традиционным способом рисования графики в LCL, а также являются наиболее важными, в то время как не-нативные классы являются дополнительными, но также очень важными. Собственные классы в основном расположены в модуле Graphics LCL и являются хорошо известными классами: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic и т.д.

TCanvas - это класс, способный выполнять рисование. Он не может существовать один и должен быть либо прикреплен к чему-то видимому (или, по крайней мере, который может быть видимым), например, к визуальному элементу управления, происходящему из TControl, либо должен быть присоединен к внеэкранному буферу потомка от TRasterImage (TBitmap является наиболее часто использующимся). TFont, TBrush и TPen описывают, как рисование различных операций будет выполняться в Canvas.

TRasterImage (обычно используется через его потомка TBitmap) - это область памяти, зарезервированная для рисования графики, но она создана для максимальной совместимости с собственным Canvas и, следовательно, в LCL-Gtk2 в X11 она расположена на сервере X11, который обеспечивает попиксельный доступ через свойство Pixels чрезвычайно медленно. В Windows это очень быстро, потому что Windows позволяет создавать локально выделенное изображение, которое может получать рисунки из Windows Canvas.

Помимо них существуют также не-нативные классы рисования, расположенные в графическом типе модулей (TRawImage), intfgraphics (TLazIntfImage) и lazcanvas (TLazCanvas, этот существует в Lazarus [с версии] 0.9.31+). TRawImage - это хранилище и описание области памяти, которая содержит изображение. TLazIntfImage - это изображение, которое присоединяется к TRawImage и обеспечивает преобразование между TFPColor и форматом настоящего пикселя TRawImage. TLazCanvas - это не нативный Canvas, который может рисовать изображение в TLazIntfImage.

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

В наборе виджетов LCL-CustomDrawn нативные классы реализованы с использованием не-нативных классов.

Все эти классы будут лучше описаны в разделах ниже.

Работа с TCanvas

Рисование прямоугольника

Многие элементы управления, например TForm, TPanel или TPaintbox, отображают свой холст как общедоступное свойство или событие OnPaint. Давайте используем TForm в качестве примера, чтобы продемонстрировать, как рисовать на холсте.

Предположим, мы хотим нарисовать красный прямоугольник с синей рамкой толщиной 5 пикселей в центре формы; размер прямоугольника должен составлять половину размера формы. Для этого мы должны добавить код в событие OnPaint формы. Не рисуйте в обработчике OnClick, потому что это рисование не является постоянным и будет стираться всякий раз, когда операционная система запрашивает перерисовку, всегда рисуйте в событии OnPaint!

Метод TCanvas для рисования прямоугольника вызывается именно так: Rectangle(). Он получает координаты краев прямоугольника либо отдельно, либо в виде записи TRect. Цвет заливки определяется цветом кисти Холста, а цвет границы задается цветом пера холста:

procedure TForm1.FormPaint(Sender: TObject);
var
  w, h: Integer;    // Ширина и высота прямоугольника
  cx, cy: Integer;  // центр формы
  R: TRect;         // запись, содержащая координаты левого, верхнего, правого, нижнего углов прямоугольника
begin
  // Высчитываем центр формы
  cx := Width div 2;
  cy := Height div 2;

  // Рассчитываем размер прямоугольника
  w := Width div 2;
  h := Height div 2;

  // Рассчитываем углы прямоугольника
  R.Left := cx - w div 2;
  R.Top := cy - h div 2;
  R.Right := cx + w div 2;
  R.Bottom := cy + h div 2;

  // Устанавливаем цвет заливки
  Canvas.Brush.Color := clRed;
  Canvas.Brush.Style := bsSolid;

  // Устанавливаем цвет границы
  Canvas.Pen.Color := clBlue;
  Canvas.Pen.Width := 5;
  Canvas.Pen.Style := psSolid;

  // Рисуем прямоугольник
  Canvas.Rectangle(R);
end;

Использование шрифта GUI по умолчанию

Это можно сделать с помощью этого простого кода:

SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));

Рисование ограниченного по ширине текста

Используйте процедуру DrawText, сначала с DT_CALCRECT, а затем без него.

// Сначала рассчитываем размер текста, затем рисуем его
TextBox := Rect(0, currentPos.Y, Width, High(Integer));
DrawText(ACanvas.Handle, PChar(Text), Length(Text), TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);

DrawText(ACanvas.Handle, PChar(Text), Length(Text), TextBox, DT_WORDBREAK or DT_INTERNAL);

Рисование текста с резкими краями (без сглаживания)

Некоторые виджеты поддерживают это через

Canvas.Font.Quality := fqNonAntialiased;

Некоторые виджеты, такие как gtk2, не поддерживают это и всегда рисуют сглаживание. Вот простая процедура рисования текста с резкими краями под gtk2. Она не предусматривает все случаи, но должна дать представление:

procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);
var
  w,h: integer;
  IntfImg: TLazIntfImage;
  Img: TBitmap;
  dy: Integer;
  dx: Integer;
  col: TFPColor;
  FontColor: TColor;
  c: TColor;
begin
  w:=0;
  h:=0;
  Canvas.GetTextSize(TheText,w,h);
  if (w<=0) or (h<=0) then exit;
  Img:=TBitmap.Create;
  IntfImg:=nil;
  try
    // рисуем текст в растровое изображение
    Img.Masked:=true;
    Img.SetSize(w,h);
    Img.Canvas.Brush.Style:=bsSolid;
    Img.Canvas.Brush.Color:=clWhite;
    Img.Canvas.FillRect(0,0,w,h);
    Img.Canvas.Font:=Canvas.Font;
    Img.Canvas.TextOut(0,0,TheText);
    // получаем изображение в памяти
    IntfImg:=Img.CreateIntfImage;
    // заменяем серые пиксели
    FontColor:=ColorToRGB(Canvas.Font.Color);
    for dy:=0 to h-1 do begin
      for dx:=0 to w-1 do begin
        col:=IntfImg.Colors[dx,dy];
        c:=FPColorToTColor(col);
        if c<>FontColor then
          IntfImg.Colors[dx,dy]:=colTransparent;
      end;
    end;
    // создаем растровое изображение
    Img.LoadFromIntfImage(IntfImg);
    // рисуем
    Canvas.Draw(x,y,Img);
  finally
    IntfImg.Free;
    Img.Free;
  end;
end;

Работа с TBitmap и другими потомками TGraphic

Объект TBitmap хранит растровое изображение, где вы можете рисовать, прежде чем показывать его на экране. Когда вы создаете растровое изображение, вы должны указать высоту и ширину, иначе это будет ноль, и ничто не будет нарисовано. И вообще, все остальные потомки TRasterImage предоставляют те же возможности. Следует использовать тот, который соответствует формату, необходимому для вывода/ввода с диска или TBitmap, в случае, если операции с дисками не будут выполняться, а также для формата Windows Bitmap (*.bmp).

Загрузка/Сохранение изображения из/на диск

Чтобы загрузить изображение с диска, используйте TGraphic.LoadFromFile и для сохранения его на другом диске, используйте TGraphic.SaveToFile. Используйте соответствующий потомок TGraphic, который соответствует ожидаемому формату. См. форматы изображений для [просмотра] списка доступных классов формата изображения.

var
  MyBitmap: TBitmap;
begin
  MyBitmap := TBitmap.Create;
  try
    // Загрузка с диска
    MyBitmap.LoadFromFile(MyEdit.Text);

    // Здесь вы можете использовать MyBitmap.Canvas для чтения/записи в/из изображения

    // Записываем обратно на другой диск
    MyBitmap.SaveToFile(MyEdit2.Text);
  finally
    MyBitmap.Free;
  end;
end;

При использовании любого другого формата процесс полностью идентичен, просто используйте соответствующий класс. Например, для изображений PNG:

var
  MyPNG: TPortableNetworkGraphic;
begin
  MyPNG := TPortableNetworkGraphic.Create;
  try
    // Загрузка с диска
    MyPNG.LoadFromFile(MyEdit.Text);

    // Здесь вы можете использовать MyPNG.Canvas для чтения/записи в/из изображения

    // Записываем обратно на другой диск
    MyPNG.SaveToFile(MyEdit2.Text);
  finally
    MyPNG.Free;
  end;
end;

Если вы заранее не знаете формат изображения, используйте TPicture, который определит формат на основе расширения файла. Обратите внимание, что TPicture не поддерживает все форматы, поддерживаемые Lazarus, с Lazarus 0.9.31 он поддерживает BMP, PNG, JPEG, Pixmap и PNM, в то время как Lazarus также поддерживает ICNS и другие форматы:

var
  MyPicture: TPicture;
begin
  MyPicture := TPicture.Create;
  try
    // Загрузка с диска
    MyPicture.LoadFromFile(MyEdit.Text);

    // Здесь вы можете использовать MyPicture.Graphic.Canvas для чтения/записи в/из изображения

    // Записываем обратно на другой диск
    MyPicture.SaveToFile(MyEdit2.Text);
  finally
    MyPicture.Free;
  end;
end;

Дополнительные форматы файлов для TImage

Вы можете добавить дополнительную поддержку форматов файлов, добавив модули fcl-image fpread* и/или fpwrite* в вашу секцию uses. Таким образом, вы можете, например, добавить поддержку TIFF для TImage

Прямой доступ к пикселям

Для непосредственного доступа к пикселям растровых изображений можно использовать внешние библиотеки, такие как BGRABitmap, LazRGBGraphics и Graphics32, или использовать нативный Lazarus'овский TLazIntfImage. Сравнение методов доступа к пикселям см. fast direct pixel access.

В некоторых наборах виджетов Lazarus (в частности, LCL-Gtk2) данные растрового изображения не сохраняются в памяти, к которой может обращаться приложение, и в общем случае собственные интерфейсы LCL рисуются только через собственные процедуры Canvas, поэтому каждая операция SetPixel / GetPixel включает медленный вызов родного Canvas API. В LCL-CustomDrawn это не так, поскольку растровое изображение локально сохраняется для всех бэкэндов, а SetPixel / GetPixel работает быстро. Для получения решения, которое работает во всех наборах виджетов, следует использовать TLazIntfImage. Поскольку Lazarus должен быть независимым от платформы и работать в gtk2, класс TBitmap не предоставляет такое свойство, как Scanline. Существует функция GetDataLineStart, эквивалентная Scanline, но доступная только для образов памяти, таких как TLazIntfImage, которая внутренне использует TRawImage.

Подводя итог, можно сказать, что со стандартным TBitmap вы можете только косвенно изменять пиксели, используя TCanvas.Pixels. Вызов собственного API для рисования / чтения отдельного пикселя, конечно, медленнее, чем прямой доступ к пикселям, особенно в LCL-gtk2 и LCL-Carbon.


Прим.перев.: Есть весьма неплохая статья (правда, на английском, чуть позже сделаю перевод и выложу на какой-нибудь ресурс), объясняющая суть работы свойства Scanline.


Рисование растровых изображений прозрачным цветом

Новая функция, реализованная в Lazarus 0.9.11, - это растровые изображения с прозрачными цветами. В растровых файлах (*.BMP) не может храниться информация о прозрачности, но они могут работать так же, как если бы вы выбрали цвет для представления прозрачной области. Это распространенный прием, используемый в приложениях Win32.

В следующем примере загружается растровое изображение из ресурса Windows, выбирается прозрачный цвет (clFuchsia) и затем он рисуется на холсте.

procedure MyForm.MyButtonOnClick(Sender: TObject);
var
  buffer: THandle;
  bmp: TBitmap;
  memstream: TMemoryStream;
begin
  bmp := TBitmap.Create;

  buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));

  if (buffer = 0) then exit; // Ошибка загрузки растрового изображения

  bmp.Handle := buffer;
  memstream := TMemoryStream.create;
  try
    bmp.SaveToStream(memstream);
    memstream.position := 0;
    bmp.LoadFromStream(memstream);
  finally
    memstream.free;
  end;

  bmp.Transparent := True;
  bmp.TransparentColor := clFuchsia;

  MyCanvas.Draw(0, 0, bmp);

  bmp.Free; // Освобождаем выделенный ресурс
end;

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

Получение скриншота экрана

Начиная с Lazarus 0.9.16, вы можете использовать LCL, чтобы делать скриншоты экрана кросс-платформенным способом. Следующий пример кода делает это (работает на gtk2 и win32, но не на gtk1 в настоящее время):

uses Graphics, LCLIntf, LCLType;
  ...
var
  MyBitmap: TBitmap;
  ScreenDC: HDC;
begin
  MyBitmap := TBitmap.Create;
  ScreenDC := GetDC(0);
  MyBitmap.LoadFromDevice(ScreenDC);
  ReleaseDC(0,ScreenDC);
  ...

Работа с TLazIntfImage, TRawImage и TLazCanvas

TLazIntfImage не является нативным эквивалентом TRasterImage (чаще используется в форме потомка TBitmap). Первое, что нужно знать об этом классе, это то, что в отличие от TBitmap, он не будет автоматически выделять область памяти для растрового изображения, сначала нужно инициализировать область памяти, а затем передать ее в TLazIntfImage. Сразу после создания TLazIntfImage нужно либо подключить его к TRawImage, либо загрузить из TBitmap.

TRawImage относится к типу объект и поэтому не нуждается ни в создании, ни в освобождении. Он может либо выделить память для самого изображения при вызове TRawImage.CreateData, либо передать блок памяти, выделенный, например, сторонней библиотекой, такой как Windows API Cocoa Framework из Mac OS X, и передать информацию об изображении в TRawImage.Description, TRawImage.Data и TRawImage.DataSize. Вместо того, чтобы прикреплять его к RawImage, можно также загрузить его из TBitmap, который будет копировать данные из TBitmap и впоследствии не будет синхронизироваться с ним. TLazCanvas не может существовать один и всегда должен быть присоединен к TLazIntfImage.

В приведенном ниже примере показано, как выбрать формат для данных и попросить TRawImage создать его для нас, а затем мы присоединим его к TLazIntfImage и потом присоединим к нему TLazCanvas:

uses graphtype, intfgraphics, lazcanvas;

var
  AImage: TLazIntfImage;
  ACanvas: TLazCanvas;
  lRawImage: TRawImage;
begin
    lRawImage.Init;
    lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight);
    lRawImage.CreateData(True);
    AImage := TLazIntfImage.Create(0,0);
    AImage.SetRawImage(lRawImage);
    ACanvas := TLazCanvas.Create(AImage);

Инициализация TLazIntfImage

Нельзя просто создать экземпляр TLazIntfImage и начать его использовать. Нужно добавить к нему хранилище. Есть 3 способа сделать это:

1. Прикрепить его к TRawImage

2. Загрузить его из TBitmap. Обратите внимание, что он скопирует память TBitmap, чтобы [экземпляр TLazIntfImage] не оставался подключенным к нему:

   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);

3. Загрузить его из описания необработанного изображения, например так:

   IntfImg := TLazIntfImage.Create(0,0);
   IntfImg.DataDescription:=GetDescriptionFromDevice(0);
   IntfImg.SetSize(10,10);

Устройство 0 в GetDescriptionFromDevice(0) использует текущий формат экрана.

TLazIntfImage.LoadFromFile

Вот пример того, как загрузить изображение непосредственно в TLazIntfImage. Он инициализирует TLazIntfImage в 32-битном формате RGBA. Имейте в виду, что это, вероятно, не собственный формат вашего экрана.

uses LazLogger, Graphics, IntfGraphics, GraphType;
procedure TForm1.FormCreate(Sender: TObject);
var
  AImage: TLazIntfImage;
  lRawImage: TRawImage;
begin
  // создаем TLazIntfImage с 32 битами на пиксель, альфа 8 бит, красный 8 бит, зеленый 8 бит, синий 8 бит,
  // порядок битов: бит 0 - это пиксель 0, сверху вниз: строка 0 - это верх
  lRawImage.Init;
  lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);
  lRawImage.CreateData(false);
  AImage := TLazIntfImage.Create(0,0);
  try
    AImage.SetRawImage(lRawImage);
    // Загружаем изображение с диска.
    // Используется расширение файла для правильного выбора изображения зарегистрированным ридером.
    // AImage будет масштабирован до  ширины и высоты загруженного изображения.
    AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');
    debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);
  finally
    AImage.Free;
  end;
end;

Загрузка TLazIntfImage в TImage

Пиксельные данные для TImage - это свойство TImage.Picture, которое имеет тип TPicture. TPicture - это мультиформатный контейнер, содержащий один из нескольких распространенных форматов изображений, таких как Bitmap, Icon, Jpeg или PNG. Обычно вы будете использовать TPicture.Bitmap для загрузки TLazIntfImage:

    Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);

Примечание:

  • Чтобы загрузить прозрачный TLazIntfImage, вы должны установить для Image1.Transparent значение true.
  • TImage использует формат экрана. Если TLazIntfImage имеет другой формат, то пиксели будут преобразованы.

Подсказка: вы можете использовать IntfImg.DataDescription:=GetDescriptionFromDevice(0); для инициализации TLazIntfImage с форматом экрана.

Пример обесцвечивания

Пример обесцвечивания с [использованием] TLazIntfImage

{ Этот код был взят из $LazarusPath/examples/lazintfimage/fadein1.lpi project. }
uses LCLType, // HBitmap type
     IntfGraphics, // TLazIntfImage type
     fpImage; // TFPColor type
...
 procedure TForm1.FadeIn(ABitMap: TBitMap);
 var
   SrcIntfImg, TempIntfImg: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
 begin
   SrcIntfImg:=TLazIntfImage.Create(0,0);
   SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempIntfImg:=TLazIntfImage.Create(0,0);
   TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
   TempBitmap:=TBitmap.Create;
   for FadeStep:=1 to 32 do begin
     for py:=0 to SrcIntfImg.Height-1 do begin
       for px:=0 to SrcIntfImg.Width-1 do begin
         CurColor:=SrcIntfImg.Colors[px,py];
         CurColor.Red:=(CurColor.Red*FadeStep) shr 5;
         CurColor.Green:=(CurColor.Green*FadeStep) shr 5;
         CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;
         TempIntfImg.Colors[px,py]:=CurColor;
       end;
     end;
     TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end;
   SrcIntfImg.Free;
   TempIntfImg.Free;
   TempBitmap.Free;
 end;

Пример конкретного формата изображения

Если вы знаете, что TBitmap использует синий 8-битный, зеленый 8-битный, красный 8-битный [каналы], вы можете напрямую получить доступ к байтам, что несколько быстрее:

uses LCLType, // HBitmap type
     IntfGraphics, // TLazIntfImage type
     fpImage; // TFPColor type
...
type
  TRGBTripleArray = array[0..32767] of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;

procedure TForm1.FadeIn2(aBitMap: TBitMap);
 var
   IntfImg1, IntfImg2: TLazIntfImage;
   ImgHandle,ImgMaskHandle: HBitmap;
   FadeStep: Integer;
   px, py: Integer;
   CurColor: TFPColor;
   TempBitmap: TBitmap;
   Row1, Row2: PRGBTripleArray;
 begin
   IntfImg1:=TLazIntfImage.Create(0,0);
   IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   IntfImg2:=TLazIntfImage.Create(0,0);
   IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);

   TempBitmap:=TBitmap.Create;
   
   //со Scanline-подобным [свойством]
   for FadeStep:=1 to 32 do begin
     for py:=0 to IntfImg1.Height-1 do begin
       Row1 := IntfImg1.GetDataLineStart(py); //как Delphi TBitMap.ScanLine
       Row2 := IntfImg2.GetDataLineStart(py); //как Delphi TBitMap.ScanLine
       for px:=0 to IntfImg1.Width-1 do begin
         Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;
         Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Исчезновение
         Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;
       end;
     end;
     IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);
     
     TempBitmap.Handle:=ImgHandle;
     TempBitmap.MaskHandle:=ImgMaskHandle;
     Canvas.Draw(0,0,TempBitmap);
   end; 

   IntfImg1.Free;
   IntfImg2.Free;
   TempBitmap.Free;
 end;

Преобразование между TLazIntfImage и TBitmap

Поскольку Lazarus не имеет свойства TBitmap.ScanLines, лучший способ быстрого доступа к пикселям изображения для чтения и записи - использование TLazIntfImage. TBitmap можно преобразовать в TLazIntfImage с помощью TBitmap.CreateIntfImage (), а после изменения пикселей он может быть преобразован обратно в TBitmap с помощью TBitmap.LoadFromIntfImage (); Вот пример того, как создать TLazIntfImage из TBitmap, изменить его и затем вернуться к TBitmap.

uses
  ...GraphType, IntfGraphics, LCLType, LCLProc,  LCLIntf ...

procedure TForm1.Button4Click(Sender: TObject);
var
  b: TBitmap;
  t: TLazIntfImage;
begin
  b := TBitmap.Create;
  try
    b.LoadFromFile('test.bmp');
    t := b.CreateIntfImage;

    // Читаем и/или записываем в пиксели
    t.Colors[10,20] := colGreen;

    b.LoadFromIntfImage(t);
  finally
    t.Free;
    b.Free;
  end;
end;

Использование не-нативного StretchDraw из LazCanvas

Как и в случае с TCanvas.StretchDraw, существует TLazCanvas.StretchDraw, но вам нужно указать интерполяцию, которую вы хотите использовать. Интерполяция, которая обеспечивает Windows-подобный StretchDraw с очень резким результатом (противоположным сглаживанию), может быть добавлена с помощью: TLazCanvas.Interpolation: = TFPSharpInterpolation.Create;

В модуле fpcanvas доступны другие [способы] интерполяции.

uses intfgraphics, lazcanvas;

procedure TForm1.StretchDrawBitmapToBitmap(SourceBitmap, DestBitmap: TBitmap; DestWidth, DestHeight: integer);
var
  DestIntfImage, SourceIntfImage: TLazIntfImage;
  DestCanvas: TLazCanvas;
begin
  // Подготовка получателя

  DestIntfImage := TLazIntfImage.Create(0, 0);
  DestIntfImage.LoadFromBitmap(DestBitmap.Handle, 0);

  DestCanvas := TLazCanvas.Create(DestIntfImage);

  //Подготовка источника
  SourceIntfImage := TLazIntfImage.Create(0, 0);
  SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);

  // Выполнение процесса растяжения [рисунка] с помощью TFPSharpInterpolation
  DestCanvas.Interpolation := TFPSharpInterpolation.Create;
  DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);

  // Перезагрузка изображения в TBitmap
  DestBitmap.LoadFromIntfImage(DestIntfImage);

  SourceIntfImage.Free;
  DestCanvas.Interpolation.Free;  
  DestCanvas.Free;
  DestIntfImage.Free;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  Bmp, DestBitmap: TBitmap;
begin
  // Подготовка получателя
  DestBitmap := TBitmap.Create;
  DestBitmap.Width := 100;
  DestBitmap.Height := 100;

  Bmp := TBitmap.Create;
  Bmp.Width := 10;
  Bmp.Height := 10;
  Bmp.Canvas.Pen.Color := clYellow;
  Bmp.Canvas.Brush.Color := clYellow;
  Bmp.Canvas.Rectangle(0, 0, 10, 10);
  StretchDrawBitmapToBitmap(Bmp, DestBitmap, 100, 100);
  Canvas.Draw(0, 0, Bmp);
  Canvas.Draw(100, 100, DestBitmap);
end;

Графика движения - как избежать мерцания

Многие программы выводят свои данные в графический интерфейс в виде 2D-графики. Если эту графику нужно быстро менять, вы скоро столкнетесь с проблемой: быстро меняющаяся графика часто мерцает на экране. Это происходит, когда пользователи иногда видят целые изображения, а иногда только когда они частично прорисованы. Это происходит потому, что процесс отрисовки требует времени.

Но как я могу избежать мерцания и получить лучшую скорость рисования? Конечно, вы можете работать с аппаратным ускорением, используя OpenGL, но этот подход довольно тяжел для небольших программ или старых компьютеров. Этот урок будет сосредоточен на рисовании в TCanvas. Если вам нужна помощь с OpenGL, взгляните на пример, который поставляется с Lazarus. Вы также можете использовать игровой пакет A.J. Venter'а, который предоставляет холст с двойной буферизацией и спрайт-компонент.

Краткую и очень полезную статью по предотвращению мерцания можно найти здесь. Хотя эти методы написаны для Delphi, они хорошо работают [и] с Lazarus.

Теперь мы рассмотрим варианты рисования на холсте:

Рисование на TImage

TImage состоит из 2 частей: TGraphic, обычно TBitmap, в котором хранится постоянное изображение и визуальная область, которые перекрашиваются на каждом OnPaint. Изменение размера TImage не изменяет размер растрового изображения. Графическое изображение (или растровое изображение) доступно через Image1.Picture.Graphic (или Image1.Picture.Bitmap). Холст - это Image1.Picture.Bitmap.Canvas. Холст визуальной области TImage доступен только во время [наступления события] Image1.OnPaint через Image1.Canvas.

Важно: Никогда не используйте событие OnPaint [компонента] Image1 для рисования в графическом/растровом изображении TImage. Графическое изображение TImage буферизуется, поэтому все, что вам нужно сделать, это нарисовать его из любого места, и изменения будут там навсегда. Однако, если вы постоянно перерисовываете, изображение будет мерцать. В этом случае вы можете попробовать другие варианты. Рисование в TImage считается медленнее, чем другие подходы.

Изменение размера растрового изображения TImage

Light bulb  Примечание: Не используйте это в событии OnPaint.
with Image1.Picture.Bitmap do begin
  Width:=100;
  Height:=120;
end;

То же самое в один присест:

with Image1.Picture.Bitmap do begin
  SetSize(100, 120);
end;

Рисование на растровом изображении TImage

Light bulb  Примечание: Не используйте это в событии OnPaint.
with Image1.Picture.Bitmap.Canvas do begin
  // заполняем все растровое изображение красным
  Brush.Color := clRed;
  FillRect(0, 0, Width, Height);
end;
Light bulb  Примечание: Внутри Image1.OnPaint'а Image1.Canvas указывает на изменчивую видимую область. За пределами Image1.OnPaint'а объект Image1.Canvas указывает на Image1.Picture.Bitmap.Canvas.

Другой пример:

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  x, y: Integer;
begin
  // Рисуем фон
  MyImage.Canvas.Pen.Color := clWhite;
  MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
   
  // Рисуем квадраты
  MyImage.Canvas.Pen.Color := clBlack;
  for x := 1 to 8 do
    for y := 1 to 8 do
      MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
        Round(x * Image.Width / 8), Round(y * Image.Height / 8));
end;

Рисование на изменчивой визуальной области TImage

Вы можете рисовать только в этой области в событии OnPaint. [Событие] OnPaint в конечном итоге автоматически вызывается [библиотекой] LCL, когда область перерисовывается. Вы можете перерисовать область вручную с помощью Image1.Invalidate. Это вызовет OnPaint не сразу, и вы можете вызывать Invalidate столько раз, сколько захотите.

procedure TForm.Image1Paint(Sender: TObject);
begin
  // рисуем линию
  Canvas.Pen.Color := clRed;
  Canvas.Line(0, 0, Width, Height);
end;

Рисование в событии OnPaint

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

procedure TForm.Form1Paint(Sender: TObject);
begin
  // рисуем линию
  Canvas.Pen.Color := clRed;
  Canvas.Line(0, 0, Width, Height);
end;

Создание пользовательского элемента управления с самостоятельной отрисовкой

Создание пользовательского элемента управления имеет преимущество структурирования вашего кода, и вы можете использовать его повторно. Этот подход очень быстрый, но он все равно может вызывать мерцание, если вы сначала не рисуете на TBitmap'е, а лишь затем [начнете] рисовать на холсте. В этом случае нет необходимости использовать событие OnPaint элемента управления.

Вот пример пользовательского элемента управления:

uses
  Classes, SysUtils, Controls, Graphics, LCLType;
 
type
  TMyDrawingControl = class(TCustomControl)
  public
    procedure EraseBackground(DC: HDC); override;
    procedure Paint; override;
  end;
 
implementation
 
procedure TMyDrawingControl.EraseBackground(DC: HDC);
begin
  // Раскомментируйте это, чтобы включить удаление фона по умолчанию
  //inherited EraseBackground(DC);
end; 
 
procedure TMyDrawingControl.Paint;
var
  x, y: Integer;
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    // Инициализируем размер растрового изображения
    Bitmap.Height := Height;
    Bitmap.Width := Width;
 
    // Рисуем фон
    Bitmap.Canvas.Pen.Color := clWhite;
    Bitmap.Canvas.Rectangle(0, 0, Width, Height);
 
    // Рисует квадраты
    Bitmap.Canvas.Pen.Color := clBlack;
    for x := 1 to 8 do
      for y := 1 to 8 do
        Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
          Round(x * Width / 8), Round(y * Height / 8));
       
    Canvas.Draw(0, 0, Bitmap);
  finally
    Bitmap.Free;
  end;
 
  inherited Paint;
end;

и как мы создаем это на форме:

procedure TMyForm.FormCreate(Sender: TObject);
begin
  MyDrawingControl := TMyDrawingControl.Create(Self);
  MyDrawingControl.Height := 400;
  MyDrawingControl.Width := 500;
  MyDrawingControl.Top := 0;
  MyDrawingControl.Left := 0;
  MyDrawingControl.Parent := Self;
  MyDrawingControl.DoubleBuffered := True;
end;

Он уничтожается автоматически, потому что мы используем Self в качестве владельца.

Установка Top и Left на ноль не обязательна, так как это стандартная позиция, но это делается для того, чтобы усилить то место, где будет помещен элемент управления.

"MyDrawingControl.Parent := Self;" - очень важная [строка кода], и вы не увидите свой элемент управления, если вы этого не сделаете.

"MyDrawingControl.DoubleBuffered := True;" требуется, чтобы избежать мерцания в Windows. Это не [оказывает никакого] влияения на GTK.

Форматы изображений

Вот таблица с правильным классом для каждого формата изображения.

Format Image class Unit
Cursor (cur) TCursor Graphics
Bitmap (bmp) TBitmap Graphics
Windows icon (ico) TIcon Graphics
Mac OS X icon (icns) TicnsIcon Graphics
Pixmap (xpm) TPixmap Graphics
Portable Network Graphic (png) TPortableNetworkGraphic Graphics
JPEG (jpg, jpeg) TJpegImage Graphics
PNM (pnm) TPortableAnyMapGraphic Graphics
Tiff (tif) TTiffImage Graphics

См. также список fcl-image supported formats.

Преобразование форматов

Иногда необходимо преобразовать один графический тип в другой. Одним из способов является преобразование графического объекта в промежуточный формат, а затем преобразование его в TBitmap. Большинство форматов могут создавать изображения из TBitmap.

Преобразование растрового изображения в PNG и сохранение его в файл:

procedure SaveToPng(const bmp: TBitmap; PngFileName: String);
var
  png : TPortableNetworkGraphic; 
begin 
  png := TPortableNetworkGraphic.Create;
  try
    png.Assign(bmp);
    png.SaveToFile(PngFileName);
  finally 
    png.Free;
  end;
end;

Пиксельные форматы

TColor

Внутренний пиксельный формат для TColor в LCL - это формат XXBBGGRR, который соответствует собственному формату Windows и противоположен большинству других библиотек, которые используют AARRGGBB. Часть XX используется для определения того, является ли цвет фиксированным цветом, в этом случае XX должен быть 00 или это индекс системного цвета. Нет места для альфа-канала.

Для преобразования из отдельных каналов RGB в TColor используйте:

RGBToColor(RedVal, GreenVal, BlueVal);

Чтобы получить каждый канал переменной TColor, используйте функции Red, Green и Blue:

RedVal := Red(MyColor);
GreenVal := Green(MyColor);
BlueVal := Blue(MyColor);

TFPColor

TFPColor использует формат AARRGGBB, общий для большинства библиотек, но он использует 16-битную глубину каждого цветового канала, что составляет 64 бита на пиксель, что является необычным. Однако это не обязательно означает, что изображения будут занимать столько памяти. Изображения, созданные с помощью TRawImage + TLazIntfImage, могут иметь любой внутренний формат хранения, а затем при операциях рисования TFPColor преобразуется в этот внутренний формат.

Модуль Graphics предоставляет процедуры для преобразования между TColor и TFPColor:

function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;
function FPColorToTColor(const FPColor: TFPColor): TColor;
function TColorToFPColor(const c: TColorRef): TFPColor; overload;
function TColorToFPColor(const c: TColor): TFPColor; overload; // не работает для системного цвета

Рисование с помощью fcl-image

Вы можете рисовать изображения, которые не будут отображаться на экране без LCL, просто используя fcl-image напрямую. Например, программа, работающая на веб-сервере без X11, может выиграть от отсутствия визуальной библиотеки в качестве зависимости. FPImage (псевдоним fcl-image) - это очень общая библиотека изображений и графики, полностью написанная на паскале. Фактически, LCL использует FPImage также для всех загрузок и сохранений из/в файлы и реализует функцию рисования через вызовы наборов виджетов (winapi, gtk, carbon, ...). Fcl-изображение с другой стороны также имеет процедуры рисования.

Для получения дополнительной информации, пожалуйста, прочитайте статью о fcl-image.

Распространенная ошибка [при использовании] OnPaint

Распространенной ошибкой, которая вызывает много ложных сообщений об багах, является вызов события Onpaint для одного объекта из другого объекта. При использовании LCL это может работать в GTK2 и Windows, но, вероятно, не удастся с Qt, Carbon и Cocoa. Обычно не нужно вызывать Invalidate. Тем не менее, иногда это может быть необходимо в процедуре Button1Click,

вот так - плохо:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Shape1Paint(Self); // Вызов события Shape1Onpaint
  Shape1.Invalidate; // Вызываем фактическую перерисовку

  ... больше кода для Button1 ...  
end;

Вот так - хорошо:

procedure TForm1.Button1Click(Sender: TObject);
begin
  ... код для Button1 ... 
  <Устанавливаем некоторые условия>; 
  // Shape1.Invalidate; // Может быть необходимо в некоторых случаях
end;

// Shape1Paint должен быть прописан в  событии OnPaint объекта shape!
procedure TForm1.Shape1Paint(Sender: TObject);
var
  Myrect: TRect;
begin   
  if <некоторое условие> then 
    with Shape1.Canvas do
    begin
      ... куча кода ...
    end;
end;

Несколько полезных примеров

Пример 1: Рисование на загруженном JPEG с [помощью] TImage

Добавьте процедуру LoadAndDraw в секцию public вашей формы и вставьте следующий код в раздел implementation:

procedure TForm1.LoadAndDraw(const sFileName: String);
var 
  jpg: TPicture;
begin
  jpg := TPicture.Create;
  try
    jpg.LoadFromFile(sFileName);
 
    Image.Picture.Bitmap.SetSize(jpg.Width, jpg.Height);
    Image.Picture.Bitmap.Canvas.Draw(0, 0, jpg.Bitmap);
 
    Image.Picture.Bitmap.Canvas.Pen.Color := clRed;
    Image.Picture.Bitmap.Canvas.Line(0, 0, 140, 140);
  finally
    jpg.Free;
  end;
end;

Пример 2: Рисование на элементах управления формы

1) Создайте новый проект New project -> Application, добавьте в раздел uses следующие модули, если необходимо: Types, Controls, Graphics.

2) Положите на форму Button1, GroupBox1 и RadioGroup1

3) Положите на GroupBox1 еще одну кнопку - Button2

4) Ваша TForm1.Create должна выглядеть так:

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to Self.ControlCount - 1 do
    RadioGroup1.Items.AddObject(Controls[i].Name, Controls[i]);

  RadioGroup1.Items.AddObject(Button2.Name,Button2);
end;

5) Для RadioGroup1 создайте обработчик события OnSelectionChanged:

procedure TForm1.RadioGroup1SelectionChanged(Sender: TObject);
begin
  Self.Repaint;
end;

6) Добавьте в секцию public вашей формы процедуру HighlightControl:

procedure TForm1.HighlightControl(AControl: TControl);
var
  R: Types.TRect;
  aCC: TControlCanvas;
begin
  R := AControl.BoundsRect;
  InflateRect(R, 2, 2);          // сделайте прямоугольник немного больше, чем элемент управления
  aCC := TControlCanvas.Create;
  aCC.Control := AControl.Parent;
  aCC.Pen.Color := clGreen;
  aCC.Pen.Width := 5;
  aCC.Pen.Style := psSolid;
  aCC.Brush.Style := bsClear;
  aCC.Rectangle(R);
  aCC.free;
end;

См. также