Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Not Just an Image List Control, Neat, 3D, iTunes Style

0.00/5 (No votes)
29 Oct 2007 1  
The image list controls shows all imaes in 3D, animate the items when selected and let control item size, transparency, positions and animation speed

Screenshot - AlbumViewer.gif

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)
{
    // centre image
    // draw the items only if they are inside the visible area
    // to keep repainting fast

    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();

    // reflection percent

    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++)
       {
           // decrease the alpha by 1 value for each line in bottom margin
           double dAlpha = (iHeight/nRef-(iHeight-iRow))*255/(iHeight/nRef);
          bmp.GetPixel(iColumn, iRow, &color);
          colorTemp.SetValue(color.MakeARGB(
             //(BYTE)(5*(iHeight/nRef-(iHeight-iRow))), 
             (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 )
    {
        //wstring wszText = m_vecItems[nItem].wszItem; 
        //Font myFont(L"Tahoma",12,FontStyleRegular,UnitPixel);
        //
        //RectF rcText;
        //grf.MeasureString(wszText.c_str(), wszText.length(), &myFont, 
    // rcRef, &rcText);
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X+rcRef.Width-rcText.Width-14, 
    // rcRef.Y+(rcRef.Height-rcText.Height)/2-rcText.Height), 
    // &SolidBrush(Color(255,255,255))); 
        //wchar_t temp[10];
        //wszText = _itow(nItem+1, temp, 10);
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X+2, rcRef.Y+(rcRef.Height-rcText.Height)/2-rcText.Height), 
    // &SolidBrush(Color(255,255,255))); 
        //grf.DrawString(wszText.c_str(),wszText.length(), &myFont, 
    // PointF(rcRef.X, rcRef.Y), &SolidBrush(Color(255,255,255))); 
    }
}

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here