Подергивание изображения при простом перемещении на канве

Тема в разделе "WASM.DirectX", создана пользователем WishMaster, 18 июн 2006.

  1. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Пускай на форме имеется кнопка, таймер и прямо на канве отображен рисунок. При нажатии на кнопку включается таймер, который с интервалом времени скажем 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-Graph Min);

    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 и упомянутой синхронизации?!



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

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Да правильно тебе советовали - надо все рисовать в памяти (с помощью DirectX и своих графических функций) и разом копировать на экран (что соб-но и делает DirectX).
     
  3. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Кстати, в том же Билдере есть примеры анимации с использованием DirectX, ничего там сложного нет.
     
  4. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Так я все и рисую в памяти, а не на канве. А уже потом использую CopyRect для копирования из BitMap'a в памяти на канву формы. То есть разговор не идет о мерцании, когда сквозь изображение мерцает фон канвы. Речь идет о подергивании зиображения при перемещении.

    Вы согнасны, что единственный выход - это синхронизация перерисовки формы с перерисовкой экрана монитора (то есть с ходом "луча")? И единственным вариантом для этого является DirectX?
     
  5. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Насчет синхронизации согласен. А реализовать можно в принципе и без DirectX - используя порты видеокарты, раньше в играх под DOS так и делали.
     
  6. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Неужели для того, чтобы плавно перемещать изображение по канве формы необходимо прямо обращаться к портам видеокарты?! Дико как то =\
     
  7. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Некоторые графические оконнные функции выполняются очень медленно и их не рекомендуют использовать в быстрой графике.



    Кстати, в Билдере в свойствах есть еще фича, связанная с перерисовкой экрана. Сейчас не помню, на этом компе Билдера нет. Попробуй поискать в хелпе на эту тему.
     
  8. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Ты имеешь ввиду в настройках проекта или где? Или это относиться каких-то функций или компонентов? Чтобы знать хоть, где рыть.
     
  9. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    В делфи это делается так.

    Form1.DoubleBuffered:=true;

    в Билдере вроде также

    Form1->DoubleBuffered=true;
     
  10. alpet

    alpet Александр

    Публикаций:
    0
    Регистрация:
    21 сен 2004
    Сообщения:
    1.221
    Адрес:
    Russia
    Попытайся немного все-таки оптимизировать код. Я работаю с графикой, посредством GDI - такая медленная анимация, должна без особых поддергиваний выполняться (помнится прога на VC++ 2003, выводила анимацию на P166). В первую очередь - избавься от бесконечной аллокации ресусров, посмотри что дает функция GdiSetBatchLimit. Вместо LineTo и MoveTo*, попробуй функцию DrawPolygon.
     
  11. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Pavia

    Спасибо, я посмотрю сейчас.



    alpet

    Спасибо, я конечно еще раз все пересмотрю. Но давайте даже не будем смотреть на тот пример, который я привел с движущимся графиком, а хотя бы разберемся с первым. Если представить себе задачу таким элементарным способом: на канву формы загружается рисунок и при нажатии на кнопку он начинает сдвигаться с заданной скоростью в какую-нибудь сторону. При этом ключевым моментом является отсутствие подергиваний (отнюдь не мерцаний, которых у меня и так нет) при перемещении. Я думаю, что эти подергивания не связаны с тормозами кода или системы, так как програмно я установил, что, например, в случае с графиком реальная перерисовка происходит каждые 30 мс (при периоде таймера 20 мс), за которые график сдвигается на 5 пикселей. Глаз видит явно не эти подергивания, так как те подергивания, про которые говорю я, можно заметить приблизительно два в секунду и расстояние от одного до другого намного больше 5 пикселей. Если скомпилируете, поймете, о чем я говорю.
     
  12. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    WishMaster

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

    Подергивания остаются, даже если создать объект TBitmap, все операции делать в нем, а на форму выводить через Copy.

    Пробуй юзать DirectX с двойным буфером и флипом.
     
  13. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    Эх, видно действительно прийдется разбираться в DirectX. Ну что ж, сейчас приступлю =) О результатах обязательно напишу.
     
  14. NaZGuL

    NaZGuL New Member

    Публикаций:
    0
    Регистрация:
    28 апр 2004
    Сообщения:
    41
    Адрес:
    Russia
    crypto



    И где это место, что-то я не нашел такого! Я сейчас прогу пишу, где GDI графика используется, так вот, у меня ~300 FPS на среднем компе!!! А количество точек при перерисовке 256*256*4!

    WishMaster

    Главное правильно использовать канву. Я юзаю три контекста: один - главный, второй - вторичный, а третий - для отрисовки текстурки. Могу только посоветовать использовать BitBlt, а точки рисовать через DIB напрямую!

    PS: Если нужно могу кинуть пример.
     
  15. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    NaZGuL

    Canvas->CopyRect(Rect(1, 1, Width - zmischennya - 6, Height - 80), Canvas, Rect(zmischennya, 1, Width - 6, Height - 80));



    Если форма достаточно большая, то и пикселей будет прилично.
     
  16. WishMaster

    WishMaster New Member

    Публикаций:
    0
    Регистрация:
    18 июн 2006
    Сообщения:
    54
    Адрес:
    Ukraine
    NaZGuL, буду очень благодарен, если кинешь. А BitBlt имеешь ввиду DirectX'овый? Потому что насколько я понял, то директ в оконном режиме не очеть большой выиграш дает...

    Пожалуйста, скинь на volkovych(собака)gmail.com
     
  17. NaZGuL

    NaZGuL New Member

    Публикаций:
    0
    Регистрация:
    28 апр 2004
    Сообщения:
    41
    Адрес:
    Russia
    WishMaster, нет я на чистом GDI пишу.

    PS: Если кто занет как конкретно юзать AlphaBlend, то поделитесь, буду очень признателен!
     
  18. alpet

    alpet Александр

    Публикаций:
    0
    Регистрация:
    21 сен 2004
    Сообщения:
    1.221
    Адрес:
    Russia
    WishMaster

    Попытайся избавиться от кода VCL, в плане операций с GDI. Эти прослойки как правило дают только тормоза. На счет DirectX - все операции рисования по сути опять же будут выполнятся с помощью GDI, и только блиттинг с помощью DirectX. Это в принципе должно ускорить анимацию на картах, где блиттинг поддерживается аппаратно, но в оконном режиме относительно ненамного.