Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Программирование игр, графики и искусственного интеллекта > Подергивание изображения при смещении на канве


Автор: NightWishMaster 18.6.2006, 19:46
Пускай на форме имеется кнопка, таймер и прямо на канве отображен рисунок. При нажатии на кнопку включается таймер, который с интервалом времени скажем 20 мс начинает сдвигать рисунок с помощью функции CopyRect или какой-нибудь другой (на самом деле здесь не суть важно) в какую-либо сторону, например влево на 5 пикселов. При этом четко видно, что изображение подергивается, при чем период таких подергиваний намного больше 20 мс, да и «размах» тоже. То есть это явно не из-за «грубости» смещений изображения. Этот эффект «рывков» возникает далеко не только при смещении изображения. Он, например, так же имеет место при последовательном рисовании квадрата с помощью функции FillRect с изменением во времени его координат и т.д.
Пикантности ситуации придает еще и тот факт, что эти подергивания видны только если смотреть непосредственно на перемещающееся изображение. Если же смотреть не на него, а на неподвижный объект (например, курсор мышки), то боковое зрение не замечает этих рывков. Создается впечатление, что эффект имеет психофизический оттенок.
Насколько я понял, этот эффект возникает не из-за ошибок в программе или тормозов системы (специально я проверял на простейших примерах, в чем вы сможете убедиться сами), а из-за соотношений частоты перерисовки изображения с частотой перерисовки всего экрана монитора. В идеале, конечно же, перерисовка изображения должна совпадать с перерисовкой всего экрана монитора и тогда этих подергиваний, скорее всего, видно не будет. 

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

Вот примеры, с которыми можно при желании поработать, посмотреть, в чем проблема и попробовать ее исправить.

1. На форме лежат Button1, Button2 и Timer1 с периодом 20. При нажатии на Button1 на форме рисуется импровизированный рисунок, а при нажатии на Button2 запускается таймер и изображение начинает смещаться, при чем видны те подергивания, про которые я говорил выше. Кнопки должны быть в нижней части экрана.

Заголовочный файл
Код

class TForm1 : public TForm
{
__published:    // IDE-managed Components
        TButton *Button1;
        TButton *Button2;
        TTimer *Timer1;
        void __fastcall Button1Click(TObject *Sender);
        void __fastcall Button2Click(TObject *Sender);
        void __fastcall Timer1Timer(TObject *Sender);
private:    // User declarations
public:        // User declarations
        __fastcall TForm1(TComponent* Owner);
};

Файл реализации:

Код

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Canvas->Brush->Style = bsSolid;
  Canvas->Brush->Color = clBlue;
  for (int a = 1; a <= Width; a += 20) Canvas->FillRect(Rect(a, 1, a + 10, Height - 80));
  Canvas->Brush->Color = clYellow;
  for (int a = 11; a <= Width; a += 20) Canvas->FillRect(Rect(a, 1, a + 10, Height - 80));
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  Timer1->Enabled = !Timer1->Enabled;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  int zmischennya = 5;
  Canvas->CopyMode = cmSrcCopy;
  Canvas->CopyRect(Rect(1, 1, Width - zmischennya - 6, Height - 80), Canvas, Rect(zmischennya, 1, Width - 6, Height - 80));
}
//---------------------------------------------------------------------------


2. На форме лежит Panel1 с Align = alTop, под панелькой лежит Button1. На панели лежит Image1 с Align = alClient, кроме того, на форме присутствует Timer1 с периодом 25. При нажатии на Button1на Image1 начинает двигаться график, на котором тоже легко усмотреть упомянутые подергивания. В отличии от первого примера, здесь все реализовано на функциях WinApi. Этот код взят с http://borland.xportal.ru/forum/viewtopic.php?t=5183&start=0&postdays=0&postorder=asc&highlight .

Заголовочный файл:

Код

class TForm1 : public TForm 
{ 
__published:   // IDE-managed Components  
        TTimer *Timer1;
        TPanel *Panel1;
        TImage *Image1;
        TButton *Button1;
        void __fastcall Timer1Timer(TObject *Sender);
        void __fastcall Button1Click(TObject *Sender);
        void __fastcall Panel1Resize(TObject *Sender);
private:   // User declarations 
public:      // User declarations 
        __fastcall TForm1(TComponent* Owner);
  void __fastcall AppendValue(double Val1); 
};

Файл реализации:

Код

TForm1 *Form1;
bool Done;

long in_rndm=1; 
float rndm() 
{ 
 static float r;
 m2: in_rndm *= 331804469l; 
     r=in_rndm*0.232832e-9+0.5; 
     if(r >=1. || r <= 0.)goto m2; 
     return r;
} 
//--------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) 
        : TForm(Owner) 
{
  Image1->Stretch=false;
}

//---------------------------------------------------------- 
double gx,gy; 
int d=5;
double OldVal1=0;
double GraphMin=0;
double GraphMax=1;
void __fastcall TForm1::AppendValue(double Val1) 
{
   Graphics::TBitmap *Bitmap1=Image1->Picture->Bitmap; 
   Bitmap1->Canvas->Lock(); 
   HDC hdc=Bitmap1->Canvas->Handle;
   ::ScrollDC(hdc,d,0,NULL,NULL,NULL,NULL);

   HPEN hpen=(HPEN)SelectObject(hdc,GetStockObject(WHITE_PEN)); 
   ::Rectangle(hdc,0,0,d,Bitmap1->Height);

   SelectObject(hdc,hpen); 

   gy = Bitmap1->Height-1.0*Bitmap1->Height*(OldVal1-GraphMin)/(GraphMax-GraphMin); 
   gx = d; 
   ::MoveToEx(hdc,gx,gy,NULL); 

   gy = Bitmap1->Height-1.0*Bitmap1->Height*(Val1-GraphMin)/(GraphMax-GraphMin); 
   gx = 0; 
   ::LineTo(hdc,gx,gy); 

   OldVal1 = Val1; 
   HDC hdcTo=GetDC(Image1->Parent->Handle); 
   ::BitBlt(hdcTo,0,0,Bitmap1->Width,Bitmap1->Height, 
          hdc,0,0,SRCCOPY); 
   ReleaseDC(Image1->Parent->Handle,hdcTo); 
   Bitmap1->Canvas->Unlock();}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  AppendValue(rndm());
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Timer1->Enabled = !Timer1->Enabled;        
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Panel1Resize(TObject *Sender)
{
   Graphics::TBitmap *Bitmap = new Graphics::TBitmap(); 
   Bitmap->Width= Image1->Width; 
   Bitmap->Height= Image1->Height; 
   Bitmap->Canvas->StretchDraw(Bitmap->Canvas->ClipRect,Image1->Picture->Bitmap); 
   Image1->Picture->Assign(Bitmap); 
   delete Bitmap; 
   Image1->Picture->Bitmap->Canvas->Pen->Color = clRed;
}
//---------------------------------------------------------------------------



Похожая проблема обсуждалась тут: http://www.wasm.ru/forum/index.php?action=vtopic&forum=11
Обсуждение свелось к использованию функций DirectX для отслеживания «обратного хода луча» и синхронизации перерисовки формы с перерисовкой всего экрана. Я в DirectX к сожалению не силен. В своей програме я использовал лишь функции Buider’a и WinApi. Неужели все действительно так сложно и плавно сдвигать рисунок на фоне невозможно без использования DirectX и упомянутой синхронизации?! 

Надеюсь, что кто-нибудь сможет что-то подсказать толковое. Заранее благодарен.
  

Автор: DragonFire 20.6.2006, 13:48
Так, я код не смотрел - времени нету, но из твоих слов понял, что ты не используешь двойную буферизацию... А это очень полезная штука и ее пожно сделать русками с момощью двсего лишь двух имайджей и нескольких строк))

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

Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ImgList;

type
  TForm1 = class(TForm)
    Button1: TButton;
    screen: TPaintBox;
    backscreen: TImage;
    Button2: TButton;
    background: TImage;
    sprite: TImage;
    FrameList: TImageList;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1:TForm1;
  a,vx,vy:integer;
  p:boolean;
  dy,x,y,dx:real;
  old,starttime:TDateTime;

const w=50;    //Размер
      s=50; //Скорость
      vs=10;  //Скорость смены кадров

implementation



{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin

  p:=false;


end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  randomize;
   screen.Refresh;
    x:=random(864)+1;
    y:=random(1023)+1;
    a:=45;
    vx:=round(sin(a)*w)+s;
    vy:=round(cos(a)*w)+s;

    button2.Visible:=false;
    p:=true;
    old:=now;
    starttime:=now;

    background.Picture.LoadFromFile('background.bmp');
  while p=true do begin


      dx:=vx*(now-old)*secsperday;
      dy:=vy*(now-old)*secsperday;
      old:=now;
      backscreen.Canvas.CopyRect(rect(0,0,screen.Width,screen.Height),background.Canvas,rect(0,0,screen.Width,screen.Height));


      x:=x+dx;
      y:=y+dy;

      if x>backscreen.Width-w then begin
          x:=backscreen.Width-w;
          vx:=-vx;
      end;
      if y>backscreen.Height-w-20 then begin
          y:=backscreen.Height-w-20;
          vy:=-vy;
      end;
      if x<w-50 then begin
          x:=0+w-50;
          vx:=-vx;
      end;
      if y<w-50 then begin
          y:=0+w-50;
          vy:=-vy;
      end;

      backscreen.Canvas.CopyRect(rect(0,0,screen.Width,screen.Height),background.Canvas,rect(0,0,screen.Width,screen.Height));
      sprite.Canvas.FillRect(rect(0,0,sprite.Width,sprite.Height));
      framelist.GetBitmap(round((now-starttime)*secsperday*vs) mod framelist.Count,sprite.Picture.Bitmap);
      backscreen.Canvas.Draw(round(x),round(y),sprite.Picture.Bitmap);
      screen.Canvas.CopyRect(rect(0,0,screen.Width,screen.Height),backscreen.Canvas,rect(0,0,backscreen.Width,backscreen.Height));
      sleep(1);
      application.ProcessMessages;
  end;
  form1.Close;

end;

end.


Добавлено @ 13:51 
Короче мысль двойной буферезации - ты берешь и все действия, все элементы рисуешь на невидимый имайдж, а потом в конце копируешь с невидимого на видимый - мерцания не будет, все круто. 
Если мнтересно читай 2 главу книги М. Мозгового "Занимательное программирование"... 

Автор: arilou 20.6.2006, 23:39
NightWishMaster


M
arilou
используйте, пожалуйста, тэг code для форматирования исходного кода
 

Автор: DragonFire 21.6.2006, 08:12
Ой, на Си написаною. я и не заметил... Но я думаю, что там ничего сложного нет - разберешся и в коде на паскале... Суть то я объснил... 

Автор: NightWishMaster 21.6.2006, 11:31
DragonFire
Благодарю, хотя в коде в своей программе (не в этих примерах, так как причина действительно не в этом) я использую двойную буферизацию. То есть я создаю Битмап, рисую все в нем, в том числе и копирую, и уже потом все это копирую на канву. Насколько я понимаю, то двойная буферизация направлена против мерцания (когда, грубо говоря, фон канвы "промигивает" сквозь изображение), у меня же рисунок при перемещении не мерцает, а подергивается. Это разные эффекты.
Вышли мне, пожалуйста, свой проект на volkovych(собака)gmail.com. Буду благодарен! 

Автор: Romikgy 21.6.2006, 12:50
NightWishMaster, почитай мож поможет
http://forum.vingrad.ru/index.php?showtopic=101260&unread=1&st 

Автор: DragonFire 21.6.2006, 13:28
Отправил, если возникнут вопросы - пиши, но вроде бы двойная буферизация должна решать все проблемы с дрожанием, подергиванием и т.д.... 

Автор: NightWishMaster 22.6.2006, 10:53
DragonFire, спасибо, сегодня достану Делфи и тогда посмотрю. Хотя я уже по-немножку начал и в DirectX ковыряться.... на всякий случай  smile 
 

Автор: DragonFire 22.6.2006, 14:26
Директа - это  вещь... 
А в делфи все просто, паскаль же - учебный язык (наверняка с ним сталкивался)... 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)