Introduction
The inspiration has been iTunes album list control. This image list control lets you display your images in a new 3D manner. No more boring images shown one after another but comes with a cool animation. The zoom level of unselected items can be controls and also the height of shadow, transparency of each unselected item, their position and animation speed.
This is a very useful control for displaying images in GUI rich applications. The control is derived from CListCtrl
class so it can be extended so support conventional list control views or cool animated 3D view.
Background
As I mentioned, the inspiration was iTunes album control. Enjoying music over one of these getting cooler weekends I wondered what would it take to have cool control for showing images with smooth and neat animation as iTunes and hence took up the challenge and found out it only needed some 10 algebra equations and good use of GDI+ to achieve it.
This control loads full size images, in the demo, it uses images as large as 800x600 and by using GDI+ efficiently you can animate, zoom and control alpha without any flicker and in a very smooth manner.
This article is most useful for developers looking for more information on using GDI+ painting. Even though GDI+ got almost everything needed but could not get the images to draw like iTunes. It allows to shear the images, however doesn't allow to make height of both sides of images different. I did not really try to use matrix transformations however I think the animation and positioning of images could be done better using them.
Using the Code
To use the control in your project, add AlubumCtrl.h and AlbumCtrl.cpp, add a list control on your form/dialog box, create a member variable for this list control of type CAlbumCtrl
and add the following code in your OnInitDialog
or anywhere else where you want to add new items to the list control:
m_ctlAlbum.AddItem(L"images\\Water lilies.jpg");
m_ctlAlbum.AddItem(L"images\\Sunset.jpg");
m_ctlAlbum.AddItem(L"images\\Winter.jpg");
m_ctlAlbum.AddItem(L"images\\Blue hills.jpg");
m_ctlAlbum.SetCurrentItem(2);
m_sldAlpha.SetRange(0,100);
m_sldAnim.SetRange(0,100);
m_sldShadow.SetRange(0,100);
m_sldZoom.SetRange(0,100);
m_sldElevation.SetRange(-300,100);
m_sldElevation.SetPos(m_ctlAlbum.GetItemElevation());
m_sldAlpha.SetPos(m_ctlAlbum.GetItemAlpha());
m_sldAnim.SetPos(m_ctlAlbum.GetAnimSpeed());
m_sldShadow.SetPos(m_ctlAlbum.GetItemShadow());
m_sldZoom.SetPos(m_ctlAlbum.GetItemZoom());
Calculating Item Positions
The following function is the most important function in placing the control and surprisingly the smallest portion of code. Well, it did take some 4 iterations of fine tuning the algebra and code before it came to this. :)
RectF CAlbumCtrl::CalcItemRect(const int n, const RectF rcBase, bool bLeft)
{
float r = m_fRatio;
float p = pow(r,n);
int h = rcBase.Height;
int w = rcBase.Width;
int y = 0;
int x = 0;
for(int j=1;j<=n;j++)
y += (m_nItemY*h*pow(r,j))/100;
if( bLeft )
{
int x = 0;
for(int j=2;j<=n+1;j++)
x += w*pow(r,j);
return RectF(rcBase.X-x, rcBase.Y-y, w*p, h*p);
}
x = rcBase.X + w;
for(int j=1;j<n;j++)
x += w*pow(r,j);
for(int j=1;j<n+1;j++)
x -= (1-r)*w*pow(r,j);
return RectF(x, rcBase.Y-y, w*p, h*p);
}
Drawing Items
This is the second most important function in the control. This picks up the items image, calculates the shadow width, applies alpha to image and decreasing alpha to the shadow and if required, places text over selected images.
void CAlbumCtrl::DrawItem
(const int nItem, RectF rc, Graphics &grf, float fAlpha, bool bDrawText)
{
if( nItem < 0 || nItem > m_vecItems.size()-1 )
return;
Bitmap bitmap(m_vecItems[nItem].wszItem.c_str());
CRect r;
GetClientRect(&r);
RectF rcWnd(r.left, r.top, r.Width(), r.Height());
if(!rcWnd.IntersectsWith(rc))
return;
Bitmap bmp(rc.Width, rc.Height, PixelFormat32bppARGB );
Graphics graphic(&bmp);
ImageAttributes imgAttrb;
RectF rcDraw(0, 0, rc.Width, rc.Height);
graphic.DrawImage(&bitmap, rcDraw, 0, 0, bitmap.GetWidth(),
bitmap.GetHeight(), UnitPixel, &imgAttrb);
graphic.DrawRectangle(&Pen(Color(80,80,80), 2),rcDraw);
ColorMatrix bmpAlpha = {
1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, fAlpha, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
imgAttrb.SetColorMatrix(&bmpAlpha);
grf.DrawImage(&bmp, rc, 0, 0, rc.Width, rc.Height, UnitPixel, &imgAttrb);
int iHeight = bmp.GetHeight();
int iWidth = bmp.GetWidth();
if( !m_nItemShadow )
m_nItemShadow = 1;
float nRef = 100.0/m_nItemShadow;
RectF rcRef = RectF(rc.X, rc.Y+rc.Height, rc.Width, rc.Height/nRef);
Bitmap bmpRef(rcDraw.Width, rcDraw.Height/nRef, PixelFormat32bppARGB );
Color color, colorTemp;
for(UINT iRow = iHeight; iRow > iHeight-iHeight/nRef; iRow--)
{
for(UINT iColumn = 0; iColumn < iWidth; iColumn++)
{
double dAlpha = (iHeight/nRef-(iHeight-iRow))*255/(iHeight/nRef);
bmp.GetPixel(iColumn, iRow, &color);
colorTemp.SetValue(color.MakeARGB(
(BYTE)dAlpha,
color.GetRed(),
color.GetGreen(),
color.GetBlue()));
bmpRef.SetPixel(iColumn, iHeight-iRow, colorTemp);
}
}
grf.DrawImage(&bmpRef, rcRef, 0, 0, rcRef.Width, rcRef.Height,
UnitPixel, &imgAttrb);
if( bDrawText )
{
}
}
Behind the Scenes Involved Maths
The most interesting portion of the control is the maths involved in positioning and animating the items. In that, there is different algebra involved in placing/animating the items on the left and right of the center selected item.
Position the Items in the Control
Calculating the Width of Any Item
Wn = W (r/100)n
Calculating the Height of Any Item
Hn = H (r/100)n
Calculating the Y Position of Any Item
Yn = Y - | n
∑
i = 1 | H/e(r/100) |
Calculating The X Position of Items on the Left of Selected Item
Xn = X - | n+1
∑
i = 2 | W(r/100) |
Calculating the X Position of Items on the Right of Selected Item
Xn = X + W + | n-1
∑
i = 1 | W(r/100)i - | n+1
∑
i = 1 | W(1-r/100)(r/100) |
where:
H
= Height of the selected item W
= Width of the selected item X
= X position of the selected item Y
= Y position of the selected item r
= Current zoom level in % e
= Current elevation of Y
Animating Items in the Control
Animating Items Towards the Left
Animating Items on the Left Side
∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X
∆Y = c(Yn+1-Yn)/l
Yn = Yn + ∆Y
∆W = c(Wn+1-Wn)/l
Wn = Wn + ∆W
∆H = c(Hn+1-Hn)/l
Hn = Hn + ∆H
Animating Items on the Right Side
∆X = c(Xn+1-Xn)/l
Xn-1 = Xn + ∆X
∆Y = c(Yn+1-Yn)/l
Yn-1 = Yn - ∆Y
∆W = c(Wn+1-Wn)/l
Wn = Wn - ∆W
∆H = c(Hn+1-Hn)/l
Hn = Hn - ∆H
Animating Items Towards the Right
Animating Items on the Left Side
∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X
∆Y = c(Yn+1-Yn)/l
Yn = Yn + ∆Y
∆W = c(Wn+1-Wn)/l
Wn = Wn + ∆W
∆H = c(Hn+1-Hn)/l
Hn = Hn + ∆H
Animating Items on the Right Side
∆X = c(Xn+1-Xn)/l
Xn = Xn + ∆X
∆Y = c(Yn+1-Yn)/l
Yn = Yn - ∆Y
∆W = c(Wn-1-Wn)/l
Wn = Wn - ∆W
∆H = c(Hn-1-Hn)/l
Hn = Hn - ∆H
where:
- c = Current animation step
- l = Total animation steps count
Future Features
I am planning to add view switch buttons so users can switch back/forth to Icon view/List View/Report View/Album View.
History
- 29th October, 2007: Initial version