This writeup is not really meant to be read on its own, in
isolation. By itself it's an unreadable, indegestible mess/mass of source code. It is part of a larger project, the root of which, including all the exposition you'll need, is to be found at:
Decoding and Animating GIFs: an implementation.
If you've jumped here FROM the root node, a basic description follows.
This writeup contains the header and implementation files for the Display Client for the Gif decoding and animation project. Much of this code is "Boilerplate" generated by the visual studio environment. When creating this project, I selected "MFC Dialog based project" in the Visual Studio appplication wizard...it then created the skeleton for this dialog class. To this I added code to create an instance of my class CGif, contained in node
Gif Animation: Class CGif. I use this object to decode a selected Gif file and animate it in the dialog window.
// GifTestHarnessDlg.h : header file
//
#pragma once
#include "gif.h"
// CGifTestHarnessDlg dialog
class CGifTestHarnessDlg : public CDialog
{
// Construction
public:
CGifTestHarnessDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_GIFTESTHARNESS_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
CDC dcBkgnd;
CBitmap *pbm;
HDC hdcScreen;
CPoint scrpt;
CBitmap* originalbackground;
BOOL bFirstTimeThru;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CGif *g;
afx_msg void OnBnClickedCancel();
afx_msg void OnTimer(UINT nIDEvent);
int CurrentFrame;
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnBnClickedOk();
};
// GifTestHarnessDlg.cpp : implementation file
//
#include "stdafx.h"
#include "GifTestHarness.h"
#include "GifTestHarnessDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CGifTestHarnessDlg dialog
CGifTestHarnessDlg::CGifTestHarnessDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGifTestHarnessDlg::IDD, pParent)
, g(NULL)
, CurrentFrame(0),bFirstTimeThru(true)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
scrpt = CPoint(5,5);
}
void CGifTestHarnessDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CGifTestHarnessDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_BN_CLICKED(IDOK, OnBnClickedOk)
ON_BN_CLICKED(IDCANCEL, OnBnClickedCancel)
ON_WM_TIMER()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// CGifTestHarnessDlg message handlers
BOOL CGifTestHarnessDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CGifTestHarnessDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CGifTestHarnessDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
if (this->g)
{
if (this->g->bGifRead)
{
CDC dcOffScreen;
CPaintDC dc(this);
CDC dcMemory;
CBitmap *bm;
int cf = this->CurrentFrame;
ImageDescriptorBlock * ib = this->g->ImageControlArray[cf];
FrameControlBlock *f = this->g->FrameControlArray[cf];
CBitmap bmm;
dcMemory.CreateCompatibleDC(&dc);
int blitwidth = this->g->blitwidth;
int blitheight = this->g->blitheight;
if (f->TransparentColorFlag )
{
if (bFirstTimeThru)
{
if(dcBkgnd.m_hDC != NULL)
{
dcBkgnd.m_hDC = NULL;
dcBkgnd.Detach();
}
dcBkgnd.CreateCompatibleDC(&dc);
CBitmap pristinebm;
pristinebm.CreateCompatibleBitmap(&dc,blitwidth,blitheight);
originalbackground = dcBkgnd.SelectObject(&pristinebm);
BOOL bres = ::BitBlt(dcBkgnd.m_hDC,0,0,blitwidth,blitheight,
dc.m_hDC,0,0,SRCCOPY);
bFirstTimeThru = false;
}
int asz = (int)g->FrameControlArray.size();
int pf = (cf == 0?asz-1:cf-1);
byte priorframedisp = g->FrameControlArray[pf]->Disposition;
COLORREF clrTransparent = PALETTERGB(f->TransColor.peRed,
f->TransColor.peGreen,
f->TransColor.peBlue);
bm = bmm.FromHandle(g->ImageControlArray[cf]->bmap);
CBitmap* pOldBitmap = dcMemory.SelectObject(bm);
dcOffScreen.CreateCompatibleDC(&dc);
CBitmap mbm;
mbm.CreateCompatibleBitmap(&dc,blitwidth,blitheight);
dcOffScreen.SelectObject(&mbm);
switch(priorframedisp)
{
case 0:
{
//disposal code 0 is "no disposal specified" in the gif spec.
//this is untested because I haven't found a GIF that uses it yet.
dcOffScreen.BitBlt(0,0,blitwidth,blitheight,&dc,0,0,SRCCOPY);
break;
}
case 1:
{
//disposal code 1 is "do not dispose" in the gif spec.
//this would be used, I imagine, for some sort of "pen" effect,
//as an animated shape was moved, it would leave a trail.
//this is untested because I haven't found a GIF that uses it yet.
dcOffScreen.BitBlt(0,0,blitwidth,blitheight,&dc,0,0,SRCCOPY);
break;
}
default:
{
//the default case encompases a prior frame disposal flag
//value of 2, probably the most common case, which
//is "restore to background color." Or, in our implementation,
// restore to background bitmap, as we have saved a copy of the
// original background bitmap in dcBkgnd. Our transparent
//blit proceeds as follows:
// step 1: blit the original background to dcOffScreen.
dcOffScreen.BitBlt(0,0,blitwidth,blitheight,&dcBkgnd,0,0,SRCCOPY);
break;
}
}
//step 2 of transparent blit...blit the actual image data, transparently,
//over the background. The "target" of this blit is our offscreen device
//context dcOffScreen.
BOOL bres =::TransparentBlt(dcOffScreen.m_hDC,
ib->left,ib->top,
ib->width, ib->height,
dcMemory.m_hDC,0,0,ib->width,ib->height,clrTransparent);
//step 3 of transparent blit...blit the offscreen device context,
//which contains the transparent image over the original background,
//to the screen. Doing it in 3 passes like this minimizes or
//eliminates screen flicker.
dc.BitBlt(0,0,blitwidth,
blitheight,&dcOffScreen,0,0,SRCCOPY);
}
else
{
//non-transparent blt for non-transparent frame
bm = bmm.FromHandle(g->ImageControlArray[cf]->bmap);
CBitmap* pOldBitmap = dcMemory.SelectObject(bm);
dc.BitBlt( ib->left,ib->top,ib->width,ib->height,&dcMemory,0,0,SRCCOPY);
}
if (this->g->FrameControlArray.size() > 1)
{
//set the timer for the delay value for the frame we
//just put up.
int fact = 10;
SetTimer(1,this->g->FrameControlArray[this->CurrentFrame]->DelayTime
* fact,NULL);
}
}
}
CDialog::OnPaint();
}
}
void CGifTestHarnessDlg::OnBnClickedCancel()
{
delete g;
OnCancel();
}
BOOL CGifTestHarnessDlg::OnEraseBkgnd(CDC* pDC)
{
if(g)
return true;
return CDialog::OnEraseBkgnd(pDC);
}
void CGifTestHarnessDlg::OnTimer(UINT nIDEvent)
{
KillTimer(nIDEvent);
if(g)
{
this->CurrentFrame++;
if (this->CurrentFrame > (int)this->g->ImageControlArray.size()-1)
this->CurrentFrame = 0;
CRect cr(0,0,g->blitwidth,g->blitheight);
this->InvalidateRect(cr,true);
}
}
void CGifTestHarnessDlg::OnBnClickedOk()
{
if(g)
{
delete(g);
g = NULL;
this->Invalidate();
bFirstTimeThru = true;
}
CFileDialog fd(true,_T("*.gif"),NULL,OFN_PATHMUSTEXIST,_T("GIF files|*.gif"),this);
fd.DoModal();
if(fd.GetFileName())
{
CString fn = fd.GetPathName();
if (!fn.IsEmpty())
{
CPaintDC dc(this);
this->g = new CGif(&fn, &dc);
this->CurrentFrame = 0;
this->Invalidate();
}
}
}