А с GDI+ даже анимированные гифы рисовать - как два пальца. В кратце расскажу как.
Для начала абстрагируемся от GDI+, мало ли потом захотим использовать другую библиотеку. Создадим небольшой интерфейс:
class CImageСоздадим реализацию этого интерфейса на основе GDI+:
{
public:
virtual CSize Size() const = 0;
virtual void Draw(HDC DC, const CRect& DestRect, const CRect& SrcRect) const = 0;
virtual ~CImage() {}
virtual size_t GetFrameCount() const = 0;
virtual void SelectActiveFrame(size_t Frame) = 0;
virtual int /*ms*/ GetFrameDelay(size_t Frame) const = 0;
};
class CGdiPlusImage : public CImage {...}Уже можно рисовать! Image.Draw(...) и вперед ;) Не забыть только вызвать в нужный момент SelectAcriveFrame() - и все заанимируется сразу.
Один товарисч из Объединенного Королевства предлагает создавать отдельный поток, чтобы там можно было через WaitEvent() дожидаться наступления следующего фрейма. Но зачем нам по отдельному потоку на каждую анимацию? Мы сделаем по-русски - через SetTimer(). Заведем будильник на время, когда нужно будет следующий фрейм отрисовать - и по WM_TIMER отрисуем. Сделаем небольшой helper-класс для этого:
class CAnimationЭтот helper выдаст нам:
{
public:
// all time/reriods are in ms
CAnimation(CImage& Image, DWORD Time = timeGetTime());
typedef std::pair<size_t /*current frame*/, DWORD /*period till next frame*/> TInfo;
TInfo CurrentInfo(DWORD Time = timeGetTime());
size_t FrameByTime(DWORD Time = timeGetTime());
DWORD PeriodTillNextFrame(DWORD CurrentTime = timeGetTime());
private:
size_t nFrames_;
DWORD Start_;
typedef std::vector<DWORD> TFrameEnds;
TFrameEnds FrameEnds_;
};
1) какой фрейм показывать на такой-то момент времени (чтобы в любой момент времени при отрисовке мы знали какой фрейм рисовать)
2) сколько осталось до показа следующего фрейма (чтобы знать на сколько нам будильник заводить).
Выдаст нам это либо по-отдельности, либо сразу (в виде TInfo).
Осталось реализовать получение этого самомго TInfo. Для этого в самом начале посчитаем когда какой фрейм заканчивается:
DWORD LastEnd = 0;тогда потом легко найдем нужный фрейм простым двоичным поиском:
for (size_t nFrame = 0; nFrame < nFrames_; ++nFrame)
{
LastEnd += Image.GetFrameDelay(nFrame);
FrameEnds_[nFrame] = LastEnd;
}
DWORD FullCyclePeriod = FrameEnds_.back();И все! Теперь при старте заводим будильник на начало второго фрейма, а при звоне будильника делаем invalidate и заводим будильник на начало следующего фрейма - и т.д. по кругу:
DWORD NormalizedTime = (Time - Start_) % FullCyclePeriod;
TFrameEnds::iterator Current(std::upper_bound(FrameEnds_.begin(), FrameEnds_.end(), NormalizedTime));
return TInfo(Current - FrameEnds_.begin(), *Current - NormalizedTime);
void OnCreate()
{
SetTimer(TimerId, Animation.PeriodTillNextFrame());
}
void OnTimer()
{
CAnimation::TInfo AnimationInfo(Animation.CurrentInfo());
Image.SelectActiveFrame(AnimationInfo.first);
SetTimer(TimerId, AnimationInfo.second);
Invalidate();
return 0;
}
Полный код примера.zip
2 комментария:
WM_TIMER не всегда подойдет. Это низкоприоритетное сообщение, которое при наличии в очереди основного потока более приоритетных сообщений может никогда и не прийти. Или придет, но с большим опозданием. Лучше все таки использовать отдельный поток, но не один на анимацию, а один на все анимации. Как-то так.
Это все так. Если отрисовка анимации - наша главная задача, то разумно использовать что-то более надежное чем WM_TIMER. А для отрисовки, например, аватаров пользователей (куда любят совать всяких прыгающих слоников и зевающих котов) - вм_таймер как раз подойдет.
Отправить комментарий