]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/animate/animate.cpp
added protection against corrupted GIFs in ReadGIF
[wxWidgets.git] / contrib / src / animate / animate.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: animate.cpp
3 // Purpose: Implementation of wxAnimation classes
4 // Author: Julian Smart and Guillermo Rodriguez Garcia
5 // Modified by:
6 // Created: 13/8/99
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart and Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif //__BORLANDC__
17
18 #include "wx/wfstream.h"
19 #include "wx/image.h"
20 #include "wx/gifdecod.h"
21 #include "wx/log.h"
22 #include "wx/dcmemory.h"
23 #include "wx/animate/animate.h"
24 #include "wx/dc.h"
25 #include "wx/dcclient.h"
26
27 /*
28 * wxAnimationPlayer
29 */
30
31 IMPLEMENT_CLASS(wxAnimationPlayer, wxObject)
32
33 wxAnimationPlayer::wxAnimationPlayer(wxAnimationBase *animation, bool destroyAnimation)
34 {
35 m_animation = animation;
36 m_destroyAnimation = destroyAnimation;
37 m_currentFrame = 0;
38 m_window = (wxWindow*) NULL;
39 m_position = wxPoint(0, 0);
40 m_looped = TRUE;
41 m_isPlaying = FALSE;
42 m_useBackgroundColour = FALSE;
43 m_customBackgroundColour = wxColour(0, 0, 0);
44 m_useCustomBackgroundColour = FALSE;
45 m_useParentBackground = FALSE;
46 m_timer.SetPlayer(this);
47 }
48
49 wxAnimationPlayer::~wxAnimationPlayer()
50 {
51 Stop();
52 ClearCache();
53
54 if (m_destroyAnimation)
55 delete m_animation;
56 }
57
58 void wxAnimationPlayer::SetAnimation(wxAnimationBase* animation, bool destroyAnimation)
59 {
60 ClearCache();
61 if (m_destroyAnimation)
62 delete m_animation;
63 m_animation = animation;
64 m_destroyAnimation = destroyAnimation;
65 }
66
67 // Play
68 bool wxAnimationPlayer::Play(wxWindow& window, const wxPoint& pos, bool WXUNUSED(looped))
69 {
70 m_window = & window;
71
72 if (!m_animation || !m_animation->IsValid())
73 return FALSE;
74
75 wxSize sz = GetLogicalScreenSize();
76 wxRect rect(pos, sz);
77 SaveBackground(rect);
78
79 if (m_frames.GetCount() == 0)
80 {
81 if (!Build())
82 {
83 wxLogWarning(_T("wxAnimationPlayer::Play: could not build the image cache."));
84 return FALSE;
85 }
86 }
87 m_currentFrame = 0;
88
89 // Create the backing store
90 m_backingStore.Create(sz.x, sz.y);
91
92 PlayFrame();
93
94 return TRUE;
95 }
96
97 // Build animation (list of wxImages). If not called before Play
98 // is called, Play will call this automatically.
99 bool wxAnimationPlayer::Build()
100 {
101 ClearCache();
102 if (m_animation)
103 {
104 int n = GetFrameCount();
105 int i;
106 for (i = 0; i < n; i++)
107 {
108 wxImage* image = GetFrame(i);
109 if (image)
110 {
111 // If the frame has transparency,
112 // set the colour so converting to a bitmap
113 // will create a mask
114 wxColour transparentColour;
115 if (GetTransparentColour(transparentColour))
116 image->SetMaskColour(transparentColour.Red(), transparentColour.Green(), transparentColour.Blue());
117
118 wxBitmap* bitmap = new wxBitmap(* image);
119 delete image;
120 if (bitmap)
121 m_frames.Append(bitmap);
122 else
123 return FALSE;
124 }
125 else
126 return FALSE;
127 }
128 return TRUE;
129 }
130 else
131 return FALSE;
132 }
133
134 // Stop the animation
135 void wxAnimationPlayer::Stop()
136 {
137 m_timer.Stop();
138 m_isPlaying = FALSE;
139 }
140
141 // Draw the current view of the animation into this DC.
142 // Call this from your OnPaint, for example.
143 void wxAnimationPlayer::Draw(wxDC& dc)
144 {
145 dc.DrawBitmap(m_backingStore, m_position.x, m_position.y);
146 }
147
148
149 int wxAnimationPlayer::GetFrameCount() const
150 {
151 if (m_animation)
152 return m_animation->GetFrameCount();
153 else
154 return 0;
155 }
156
157 wxImage* wxAnimationPlayer::GetFrame(int i) const
158 {
159 if (m_animation)
160 return m_animation->GetFrame(i);
161 else
162 return (wxImage*) NULL;
163 }
164
165 wxAnimationDisposal wxAnimationPlayer::GetDisposalMethod(int i) const
166 {
167 if (m_animation)
168 return m_animation->GetDisposalMethod(i);
169 else
170 return wxANIM_UNSPECIFIED;
171 }
172
173 wxRect wxAnimationPlayer::GetFrameRect(int i) const
174 {
175 if (m_animation)
176 return m_animation->GetFrameRect(i);
177 else
178 return wxRect(0, 0, 0, 0);
179 }
180
181 int wxAnimationPlayer::GetDelay(int i) const
182 {
183 if (m_animation)
184 return m_animation->GetDelay(i);
185 else
186 return 0;
187 }
188
189 wxSize wxAnimationPlayer::GetLogicalScreenSize() const
190 {
191 if (m_animation)
192 return m_animation->GetLogicalScreenSize();
193 else
194 return wxSize(0, 0);
195 }
196
197 bool wxAnimationPlayer::GetBackgroundColour(wxColour& col) const
198 {
199 if (m_animation)
200 return m_animation->GetBackgroundColour(col);
201 else
202 return FALSE;
203 }
204
205 bool wxAnimationPlayer::GetTransparentColour(wxColour& col) const
206 {
207 if (m_animation)
208 return m_animation->GetTransparentColour(col);
209 else
210 return FALSE;
211 }
212
213 // Play the frame
214 bool wxAnimationPlayer::PlayFrame(int frame, wxWindow& window, const wxPoint& WXUNUSED(pos))
215 {
216 wxMemoryDC dc;
217 dc.SelectObject(m_backingStore);
218
219 // Draw the background: colour or area beneath animation
220 wxColour col(255, 255, 255);
221
222 if (UsingBackgroundColour())
223 {
224 if (UsingCustomBackgroundColour())
225 col = GetCustomBackgroundColour();
226 else
227 {
228 GetBackgroundColour(col);
229 }
230
231 // Draw the background colour loaded from the animation
232 // (or set by the user)
233 DrawBackground(dc, wxPoint(0, 0), col);
234 }
235 else
236 {
237 // Draw background we saved
238 dc.DrawBitmap(m_savedBackground, 0, 0);
239 }
240
241 // Draw all intermediate frames that haven't been removed from the
242 // animation
243 int i;
244 for (i = 0; i < frame; i++)
245 {
246 if ((GetDisposalMethod(i) == wxANIM_DONOTREMOVE) || (GetDisposalMethod(i) == wxANIM_UNSPECIFIED))
247 {
248 DrawFrame(i, dc, wxPoint(0, 0));
249 }
250 }
251 DrawFrame(frame, dc, wxPoint(0, 0));
252
253 dc.SelectObject(wxNullBitmap);
254
255 // Draw from backing bitmap onto window
256 wxClientDC clientDC(& window);
257 Draw(clientDC);
258
259 return TRUE;
260 }
261
262 bool wxAnimationPlayer::PlayFrame()
263 {
264 m_isPlaying = TRUE;
265
266 PlayFrame(GetCurrentFrame(), * GetWindow(), GetPosition());
267
268 // Set the timer for the next frame
269 int delay = GetDelay(GetCurrentFrame());
270 if (delay == 0)
271 delay = 1; // 0 is invalid timeout for wxTimer.
272
273 m_timer.Start(delay);
274
275 m_currentFrame ++;
276
277 if (m_currentFrame == GetFrameCount())
278 {
279 // Should a non-looped animation display the last frame?
280 if (!m_looped)
281 {
282 m_timer.Stop();
283 m_isPlaying = FALSE;
284 }
285 else
286 m_currentFrame = 0;
287 }
288
289 return TRUE;
290 }
291
292 // Clear the wxImage cache
293 void wxAnimationPlayer::ClearCache()
294 {
295 wxList::compatibility_iterator node = m_frames.GetFirst();
296 while (node)
297 {
298 wxList::compatibility_iterator next = node->GetNext();
299 wxBitmap* bitmap = (wxBitmap*) node->GetData();
300 delete bitmap;
301 m_frames.Erase(node);
302 node = next;
303 }
304 }
305
306 // Draw the background colour
307 void wxAnimationPlayer::DrawBackground(wxDC& dc, const wxPoint& pos, const wxColour& colour)
308 {
309 wxASSERT_MSG( (m_animation != NULL), _T("Animation not present in wxAnimationPlayer"));
310 wxASSERT_MSG( (m_frames.GetCount() != 0), _T("Animation cache not present in wxAnimationPlayer"));
311
312 // Optimization: if the first frame fills the whole area, and is non-transparent,
313 // don't bother drawing the background
314
315 wxBitmap* firstBitmap = (wxBitmap*) m_frames.GetFirst()->GetData() ;
316 wxSize screenSize = GetLogicalScreenSize();
317 if (!firstBitmap->GetMask() && (firstBitmap->GetWidth() == screenSize.x) && (firstBitmap->GetHeight() == screenSize.y))
318 {
319 return;
320 }
321
322 wxBrush brush(colour, wxSOLID);
323 wxPen pen(colour, 1, wxSOLID);
324 dc.SetBrush(brush);
325 dc.SetPen(pen);
326 dc.SetLogicalFunction(wxCOPY);
327
328 dc.DrawRectangle(pos.x, pos.y, screenSize.x, screenSize.y);
329 }
330
331 // Save the pertinent area of the window so we can restore
332 // it if drawing transparently
333 void wxAnimationPlayer::SaveBackground(const wxRect& rect)
334 {
335 wxASSERT( GetWindow() );
336
337 if (!GetWindow())
338 return;
339
340 m_savedBackground.Create(rect.width, rect.height);
341
342 wxMemoryDC memDC;
343 memDC.SelectObject(m_savedBackground);
344
345 if (m_useParentBackground && GetWindow()->GetParent())
346 {
347 wxWindow* parent = GetWindow()->GetParent();
348 wxClientDC dc(parent);
349
350 // Translate the point to coordinates in the
351 // parent's client area, going via screen coordinates
352 wxPoint pt(rect.x, rect.y);
353 wxPoint screenPt = GetWindow()->ClientToScreen(pt);
354 wxPoint parentPt = parent->ScreenToClient(screenPt);
355
356 memDC.Blit(0, 0, rect.width, rect.height, & dc, parentPt.x, parentPt.y);
357 }
358 else
359 {
360 wxClientDC dc(GetWindow());
361
362 memDC.Blit(0, 0, rect.width, rect.height, & dc, rect.x, rect.y);
363 }
364 memDC.SelectObject(wxNullBitmap);
365 }
366
367 // Draw this frame
368 void wxAnimationPlayer::DrawFrame(int frame, wxDC& dc, const wxPoint& pos)
369 {
370 wxASSERT_MSG( (m_animation != NULL), _T("Animation not present in wxAnimationPlayer"));
371 wxASSERT_MSG( (m_frames.GetCount() != 0), _T("Animation cache not present in wxAnimationPlayer"));
372 wxASSERT_MSG( !!m_frames.Item(frame), _T("Image not present in wxAnimationPlayer::DrawFrame"));
373
374 wxBitmap* bitmap = (wxBitmap*) m_frames.Item(frame)->GetData() ;
375
376 wxRect rect = GetFrameRect(frame);
377
378 dc.DrawBitmap(* bitmap, pos.x + rect.x, pos.y + rect.y, (bitmap->GetMask() != NULL));
379 }
380
381 void wxAnimationTimer::Notify()
382 {
383 m_player->PlayFrame();
384 }
385
386 /*
387 * wxAnimationBase
388 */
389
390 IMPLEMENT_ABSTRACT_CLASS(wxAnimationBase, wxObject)
391
392 /*
393 * wxGIFAnimation
394 */
395
396 IMPLEMENT_CLASS(wxGIFAnimation, wxAnimationBase)
397
398 wxGIFAnimation::wxGIFAnimation()
399 {
400 m_decoder = (wxGIFDecoder*) NULL;
401 }
402
403 wxGIFAnimation::~wxGIFAnimation()
404 {
405 delete m_decoder;
406 }
407
408 int wxGIFAnimation::GetFrameCount() const
409 {
410 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
411
412 return m_decoder->GetNumberOfFrames();
413 }
414
415 wxImage* wxGIFAnimation::GetFrame(int i) const
416 {
417 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
418
419 m_decoder->GoFrame(i + 1);
420
421 wxImage* image = new wxImage;
422 m_decoder->ConvertToImage(image);
423 return image;
424 }
425
426 wxAnimationDisposal wxGIFAnimation::GetDisposalMethod(int i) const
427 {
428 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
429
430 m_decoder->GoFrame(i + 1);
431
432 int disposalMethod = m_decoder->GetDisposalMethod();
433 return (wxAnimationDisposal) disposalMethod;
434 }
435
436 wxRect wxGIFAnimation::GetFrameRect(int i) const
437 {
438 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
439
440 m_decoder->GoFrame(i + 1);
441
442 wxRect rect(m_decoder->GetLeft(), m_decoder->GetTop(), m_decoder->GetWidth(), m_decoder->GetHeight());
443 return rect;
444 }
445
446 int wxGIFAnimation::GetDelay(int i) const
447 {
448 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
449
450 m_decoder->GoFrame(i + 1);
451 return m_decoder->GetDelay();
452 }
453
454 wxSize wxGIFAnimation::GetLogicalScreenSize() const
455 {
456 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
457
458 return wxSize(m_decoder->GetLogicalScreenWidth(), m_decoder->GetLogicalScreenHeight());
459 }
460
461 bool wxGIFAnimation::GetBackgroundColour(wxColour& col) const
462 {
463 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
464
465 int i = m_decoder->GetBackgroundColour();
466 if (i == -1)
467 return FALSE;
468 else
469 {
470 unsigned char* pal = m_decoder->GetPalette();
471
472 if (pal)
473 {
474 col = wxColour(pal[3*i + 0], pal[3*i + 1], pal[3*i + 2]);
475 return TRUE;
476 }
477 else
478 return FALSE;
479 }
480 }
481
482 bool wxGIFAnimation::GetTransparentColour(wxColour& col) const
483 {
484 wxASSERT_MSG( (m_decoder != (wxGIFDecoder*) NULL), _T("m_decoder must be non-NULL"));
485
486 int i = m_decoder->GetTransparentColour();
487 if (i == -1)
488 return FALSE;
489 else
490 {
491 unsigned char* pal = m_decoder->GetPalette();
492
493 if (pal)
494 {
495 col = wxColour(pal[3*i + 0], pal[3*i + 1], pal[3*i + 2]);
496 return TRUE;
497 }
498 else
499 return FALSE;
500 }
501 }
502
503 bool wxGIFAnimation::IsValid() const
504 {
505 return ((m_decoder != NULL) && (m_decoder->IsAnimation()));
506 }
507
508 bool wxGIFAnimation::LoadFile(const wxString& filename)
509 {
510 if (m_decoder)
511 delete m_decoder;
512 m_decoder = NULL;
513
514 if (wxFileExists(filename))
515 {
516 wxFileInputStream stream(filename);
517 m_decoder = new wxGIFDecoder(& stream, TRUE);
518
519 if (m_decoder->ReadGIF() != wxGIF_OK)
520 {
521 delete m_decoder;
522 m_decoder = NULL;
523 return FALSE;
524 }
525
526 if (!m_decoder->IsAnimation())
527 {
528 delete m_decoder;
529 m_decoder = NULL;
530
531 return FALSE;
532 }
533 else
534 return TRUE;
535 }
536 else
537 return FALSE;
538 }
539
540 /*
541 * wxAnimationCtrlBase
542 * Abstract base class for format-specific animation controls.
543 * This class implements most of the functionality; all a derived
544 * class has to do is create the appropriate animation class on demand.
545 */
546
547 IMPLEMENT_ABSTRACT_CLASS(wxAnimationCtrlBase, wxControl)
548
549 BEGIN_EVENT_TABLE(wxAnimationCtrlBase, wxControl)
550 EVT_PAINT(wxAnimationCtrlBase::OnPaint)
551 END_EVENT_TABLE()
552
553 bool wxAnimationCtrlBase::Create(wxWindow *parent, wxWindowID id,
554 const wxString& filename, const wxPoint& pos,
555 const wxSize& size, long style, const wxString& name)
556 {
557 m_animation = NULL;
558 m_filename = filename;
559
560 if (!wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name))
561 return FALSE;
562
563 SetBackgroundColour(parent->GetBackgroundColour());
564
565 m_animationPlayer.SetCustomBackgroundColour(GetBackgroundColour());
566
567 // Want to give the impression of transparency by painting
568 // the parent background
569 // if (parent)
570 // m_animationPlayer.UseParentBackground(TRUE);
571 m_animationPlayer.SetWindow(this);
572 m_animationPlayer.SetPosition(wxPoint(0, 0));
573 m_animationPlayer.SetDestroyAnimation(FALSE);
574
575 LoadFile(filename);
576
577 return TRUE;
578 }
579
580 wxAnimationCtrlBase::~wxAnimationCtrlBase()
581 {
582 if (m_animationPlayer.IsPlaying())
583 m_animationPlayer.Stop();
584 m_animationPlayer.SetAnimation(NULL, FALSE);
585 delete m_animation;
586 }
587
588 bool wxAnimationCtrlBase::LoadFile(const wxString& filename)
589 {
590 if (m_animationPlayer.IsPlaying())
591 m_animationPlayer.Stop();
592
593 wxString filename1(filename);
594
595 if (filename1.IsEmpty())
596 filename1 = m_filename;
597
598 if (filename1.IsEmpty())
599 return FALSE;
600
601 if (m_animation)
602 {
603 delete m_animation;
604 m_animation = NULL;
605 }
606
607 m_animation = DoCreateAnimation(filename1);
608 if (!m_animation)
609 return FALSE;
610
611 if (!m_animation->LoadFile(filename) || !m_animation->IsValid())
612 {
613 delete m_animation;
614 m_animation = NULL;
615 return FALSE;
616 }
617 m_animationPlayer.SetAnimation(m_animation, FALSE);
618
619 if (GetWindowStyle() & wxAN_FIT_ANIMATION)
620 FitToAnimation();
621
622 return TRUE;
623 }
624
625 bool wxAnimationCtrlBase::Play(bool looped)
626 {
627 return m_animationPlayer.Play(*this, wxPoint(0, 0), looped);
628 }
629
630 wxSize wxAnimationCtrlBase::DoGetBestSize() const
631 {
632 if (m_animationPlayer.HasAnimation() && (GetWindowStyle() & wxAN_FIT_ANIMATION))
633 {
634 return m_animationPlayer.GetLogicalScreenSize();
635 }
636 else
637 {
638 return GetSize();
639 }
640 }
641
642 void wxAnimationCtrlBase::FitToAnimation()
643 {
644 if (!m_animationPlayer.HasAnimation())
645 return;
646
647 wxSize sz = m_animationPlayer.GetLogicalScreenSize();
648 SetClientSize(sz);
649 }
650
651 void wxAnimationCtrlBase::OnPaint(wxPaintEvent& WXUNUSED(event))
652 {
653 wxPaintDC dc(this);
654
655 if (GetPlayer().IsPlaying())
656 {
657 GetPlayer().Draw(dc);
658 }
659 }
660
661 /*
662 * wxGIFAnimationCtrl
663 * Provides a GIF animation class when required.
664 */
665
666 IMPLEMENT_ABSTRACT_CLASS(wxGIFAnimationCtrl, wxAnimationCtrlBase)
667
668 wxAnimationBase* wxGIFAnimationCtrl::DoCreateAnimation(const wxString& WXUNUSED(filename))
669 {
670 return new wxGIFAnimation;
671 }