Помощь - Поиск - Пользователи - Календарь
Полная версия: Быстрая анимация посредством Delphi
Форум «Всё о Паскале» > Delphi, Assembler и другие языки. > Delphi
Gothic_Snake
Существует поверье, что написание анимации посредством Canvas в Delphi очень неблагодарное дело, мол тормоза, мерцание... Кул программеры сразу начинают изучать OpenGL/DX
На это есесно уходит многа времени...
Хочу привести пару примеров работы в Delphi с TCanvas+GDI
Ниже приведен пример заставки с часиками

Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Buttons,commctrl;
var
  Window:HWND;
  DC:Hdc;
  DCCanv:TCanvas;
const
  SnowCount=2000;
type
  TSnow=class
    dc:Integer;
    col:Tcolor;
    cnv:Tcanvas;
    X,Y:Integer;
    Dx:Integer;
    dy:Integer;
    widht,Height:Integer;
    Constructor Create;
    Procedure Draw;
  end;
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
    f:Boolean;
    Snows:array of TSnow;
    col:Tcolor;
    dc:Integer;
    buf:TBitmap;
    x:Integer;
    dx:integer;
    Function Rct:Trect;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  i:integer;
  fx:byte;
begin
  x:=5;
  dx:=5;
  dc:=1;
  ShowCursor(false);
  buf:=TBitmap.Create;
  SetLength(snows,snowcount);
  for i:=0 to snowcount -1 do
  begin
    Snows[i]:=TSnow.Create;
    with Snows[i] do
    begin
      Dx:=random(3)-random(3);
      dy:=random(3)+1;
      widht:=Screen.Width;
      Height:=Screen.Height;
      X:=random(Screen.Width);
      Y:=random(Screen.Height);
      cnv:=buf.Canvas;
      fx:=random(256);
      col:=rgb(fx,fx,fx);
    end;
  end;
  Left:=0;
  Top:=0;
  Width:=Screen.Width;
  Height:=Screen.Height;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  buf.Width:=PaintBox1.Width;
  buf.Height:=PaintBox1.Height;
end;

function TForm1.Rct: Trect;
begin
  Result:=PaintBox1.ClientRect;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  i:integer;
begin
  col:=rgb(GetrValue(col)+dc,GetgValue(col)+dc,GetBValue(col)+dc);
  if (col=$00FFFFff)or(col=0) then dc:=-dc;
  with buf.Canvas do
  begin
    Brush.Color:=clBlack;
    FillRect(Rct);
    Font.Color:=COL;
    Font.Name:='Courier New';
    font.Size:=70;
    x:=x+dx;
    if (x>460)or(x=5) then dx:=-dx;
    TextOut(x,600,'© By Snake');
    font.Size:=50;
    TextOut(x+120,700,timetostr(now));
  end;
  for i:=0 to SnowCount-1 do
    Snows[i].Draw;
  PaintBox1.Canvas.CopyRect(rct,buf.Canvas,rct);
end;

constructor TSnow.Create;
begin
  dc:=1;
end;

procedure TSnow.Draw;
var
  fx:byte;
begin
  col:=rgb(GetrValue(col)+dc,GetgValue(col)+dc,GetBValue(col)+dc);
  if (col=$00ffffff)or(col=0) then dc:=-dc;
  x:=x+dx+random(3)-random(3);
  y:=y+dy;
  if (x>=widht)or(x<0)or(y>=Height) then
  begin
    dx:=random(3)-random(3);
    dy:=random(3)+1;
    y:=0;
    x:=random(screen.Width);
    fx:=random(256);
    col:=rgb(fx,fx,fx);
  end;
  cnv.Pixels[x,y]:=col;
end;

procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if not f then
  begin
    f:=true;
    exit;
  end;

end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  close;
end;

procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  close;
end;

end.

frm

object Form1: TForm1
  Left = 215
  Top = 132
  BorderStyle = bsNone
  Caption = 'Form1'
  ClientHeight = 447
  ClientWidth = 644
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  KeyPreview = True
  OldCreateOrder = False
  Position = poDefault
  Visible = True
  OnCreate = FormCreate
  OnKeyPress = FormKeyPress
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object PaintBox1: TPaintBox
    Left = 0
    Top = 0
    Width = 644
    Height = 447
    Align = alClient
    OnMouseDown = PaintBox1MouseDown
  end
  object Timer1: TTimer
    Interval = 1
    OnTimer = Timer1Timer
    Left = 8
    Top = 8
  end
end
Gothic_Snake
По просьбе знакомого пишу этот пост.

В дельфи в классе TCanvas существует метод TCanvas.pixels[x,y], с помошью которого можно получить цвет пикселя на холсте. Этот метод очень медленно работает из-за перерисовки всего холста после каждой операции считывания/записи. Существует и другой способ доступа к пикселю на холсте. Этот доступ намного быстрее, так как работает напрямую с блоком бамяти, где распологается карта битов изображения. Далее привожу метод реализации этого способа:
type
TRGB24=record //описание записи для чтения. Внимание расположение цветов не как в TColor!
b,//1
g,//2
r:byte;//3
end;
ARGB24=array [0..0] of TRGB24;
PRGB24=^ARGB24;

//аналогично:
TRGB32=record
b,//1
g,//2
r,//3
a:byte;//4 - Alpha канал
end;
ARGB32=array [0..0] of TRGB32;
PRGB32=^ARGB32;

TRGB16=record //режим 5-5-5 или 5-6-5
bg,//1
gr:Byte//2
end;
ARGB16=array [0..0] of TRGB16;
PRGB16=^ARGB16;

//далее пример процедуры FadeDown для 24 битового изображения
procedure TForm1.FadeDown;
var
BitMap : TBitMap;
x,y : Integer;
p : PRGB;
step:byte;
begin
bitmap:=tbitmap.create;
BitMap.LoadFromFile('test.bmp');
try
BitMap.PixelFormat:=pf24bit;
// Указываем битмапу, что изображения в нашем случае 24 бита!
// иначе может привести к ошибке, в случае, если изображение не является 24-битным
for step:=0 to 255 do
begin
for y := 0 to BitMap.Height -1 do
begin
P := BitMap.ScanLine[y];//считываем линию в указатель на массив P
for x := 0 to BitMap.Width-1 do
begin
if p[x].r>0 then dec(p[x].r) else p[x].r:=0;//Обращение поэлементно к каждому цвету
if p[x].g>0 then dec(p[x].g) else p[x].g:=0;//Обращение поэлементно к каждому цвету
if p[x].b>0 then dec(p[x].b) else p[x].b:=0;//Обращение поэлементно к каждому цвету
end;
end;
BitBlt(Canvas.Handle,0,0,Width,Height,BitMap.Canvas.Handle,0,0,SRCCOPY);
//Обновление изображения Обязательно! иначе результат виден не будет!
end;
finally
bitmap.free;
end;
end;


Ну вот вроде бы и все, что хотел бы сказать, а дальше-у кого-какая фантазия!
NorthAngel
Цитата(Gothic_Snake @ 23.12.2005 2:04) *

Существует поверье, что написание анимации посредством Canvas в Delphi очень неблагодарное дело, мол тормоза, мерцание... Кул программеры сразу начинают изучать OpenGL/DX
На это есесно уходит многа времени...
Хочу привести пару примеров работы в Delphi с TCanvas+GDI
Ниже приведен пример заставки с часиками

Пример, конечно, хороший... но нет ли у кого-нибудь статей и т.п. по анимации посредством Canvas в Delphi...
Надоели уже тормоза из-за анимации... mad.gif Не знаю что делать...
GoodWind
NorthAngel, вот я и раскручиваю Gothic_Snake`a на серию статей =)
Gothic_Snake
2NorthAngel
Хорошо. Быстрая анимация без тормозов?
По средством Канваса в Дельфи можно написать анимацию только в разумных пределах. Это из-за того, что битлидинг (копирование одной области видеопямяти в другую) происходит с исполизование процессора (CPU). В отличии от DirectX/OpenGL, где на прорисовку кадра можно направить графический процессов вашей видеокары (GPU), который работает только со своей пямятью (видеопямятью), что соответственно работает намного быстрее. Вы спросите: Почему в досе это делается быстрее? Отвечаю: В досе у вас есть доступ напрямую к видеопамяти (адрес страницы). В Windows же это происходит через API интервейс, и напрямую к видеопамяти обратиться вам никто не даст.
Раз затронулась такакя тема, то как же происходит рисование в Windows? - Посредством API функций GDI и GDI+ (GDI+ это нововведение со времен WinXP/Win2003). Класс TCanvas в Delphi - это обыкновенная "обертка" этих функций.

Как происходит быстое рисование:
Идея такова, и еще лучше пока еще не придумали:
Сначало все рисуется в памяти, далее содержимое памяти копирутся в видео пямять (идея 2 буфферизации). Как это делается в дельфи:
Код

...
uses graphics //! переопределяется класс TBitmap
...
var
  bmBackBuff:TBitmap//Задний буфер. В роли видеопямяти у нас будет канвас нашей формы.
...
Procedure TForm1.formCreate(sender:TObject);
begin
  ...
  bmBackBuff:=TBitmap.Create;//Создаем задний буфер и выделяем под него пямять в озу
  bmBackBuff.width:=self.width; //задаем размер
  bmBackBuff.height:=self.height;//нашему заднему буферу, иначе мы будем рисовать в пространстве 0х0 пикселей :)
  ...
end;

Procedure Flip;//Смена страниц :)
begin
  BitBlt(form1.canvas.handle,//Куда копируем изображение
        0,0,//С какого пикселя начать
        bmBackBuff.width,bmBackBuff.width,//размер копируемого изображения
        bmBackBuff.canvas.handle,//откуда копируем
        SRCCOPY);//тип копирования (SRCCOPY/SRCPAINT/SRCINVERT/SRCCLEAR/SRCAND)
end;

Procedure Draw;//здесь мы будем рисовать
begin
   //Производим рисование на задний буффер
  with bmBackBuff.canvas do
  begin
    brush.color:=clblack;
    fillrect(0,0,bmbackbuff.width,bmbackbuff.height);
    font.size:=72;
    font.name:='Courier New';
    textout(10,10,timetostr(NOW));
    //и т.д.
  end;
end;

Procedure Render;//Как звучит! :)
begin
  Draw;
  Flip;
end;

procedure tform1.formshow(sender:TObject);
begin
  render;
end;

Далее, у кого какая фантазия...
Это чисто теоритический пример. Может и с нек. ошибками, главное- идея...
Главное при построении кадра-это оптимизиронанный и быстрый код...
hardcase
Такая же смена страниц происходит в VCL при прорисовке компонента, если у него стоит DoubleBuffered в True.


Canvas - вещь хорошая, но как-никак медленная. (иногда глючная - о потере контекста молчу)
Вот мелкософт сделал уже давно GDI+. На мой взгляд хорошая вешь, гораздо удобнее написания графики на обычном GDI с использованием известных апишек. Кроме того GDI+ - объектно ориетрированная библиотека и использует в конечном счёте DX.
Gothic_Snake
Цитата(hardcase @ 8.01.2006 3:02) *

Такая же смена страниц происходит в VCL при прорисовке компонента, если у него стоит DoubleBuffered в True.
Canvas - вещь хорошая, но как-никак медленная. (иногда глючная - о потере контекста молчу)
Вот мелкософт сделал уже давно GDI+. На мой взгляд хорошая вешь, гораздо удобнее написания графики на обычном GDI с использованием известных апишек. Кроме того GDI+ - объектно ориетрированная библиотека и использует в конечном счёте DX.

GDI+ никогда DX не использует. Разве что часть DD да и то я совневаюсь в этом...
hardcase
Пардон, DD. Извините - очепятался.
DD использует - c хедерами даже модуль поставляется DirectDraw.
Там кое-какие интефейсы прописываются...
Gothic_Snake
Цитата(hardcase @ 9.01.2006 0:33) *

Пардон, DD. Извините - очепятался.
DD использует - c хедерами даже модуль поставляется DirectDraw.
Там кое-какие интефейсы прописываются...

Бывает... А есть у тя эти хедеры? Скинь если не трудно..
hardcase
Цитата(Gothic_Snake @ 10.01.2006 16:11) *

Бывает... А есть у тя эти хедеры? Скинь если не трудно..

Вот джедайские хедеры с сэмплами.

Кончно, реализация классов не очень удобна.
Я в своих пограммах писал собстверрые классы-адаптеры для уже объявленных. Просто модель классов, начисто скопирована из gdiplus.h, написанного на C++

Но в целом библиотека гораздо удобнее чем обычные API.
Gothic_Snake
Большое спасибо... Постараюсь порыться в них =)
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.