Click here to Skip to main content
14,925,595 members
Articles / Desktop Programming / MFC
Article
Posted 6 Oct 2015

Stats

40.5K views
7.5K downloads
78 bookmarked

Advanced Image Control

Rate me:
Please Sign up or sign in to vote.
4.94/5 (39 votes)
18 Jun 2016CPOL9 min read
Image Control for viewing most common image formats with advanced features included (Import Image, Preview, Resize, Position, Pan, Zoom, Export Image, Extract Resource Icon).

Image 1


Table of contents

  1. Introduction
  2. The CImageCtrl class
  3. PAN and ZOOM
  4. GDI+
  5. GDI: In-memory Device Context
  6. Resources
       6.1 Resource-Only DLL
       6.2 ResourceList module
       6.3 Icon module
       6.4 Load image from resource DLL or EXE
  7. Resizable Dialog
  8. Background
  9. References
10. Using the code
11. History

 

Introduction

This article describes Image Control (derived from CStatic), primary used to import and view following image formats: BMP, DIB, JPG, JPEG, JPE, JFIF, GIF, TIF, TIFF, PNG, ICO, WMF, EMF. What is more, there are also some other handy features integrated like Resize, Position, Pan, Zoom, Export Image and Extract Resource Icon.

I took the basics from Tobias Eiseler's solution1 and improved it even more for purposes of importing all mentioned image types from dll or exe resources with additional resizing and positioning (inside client area) option.

The goal was to make an advanced image control, where image can be obtained from one of these four sources:

1) Image file path,
2) Image IStream interface,
3) Image BYTE(unsigned char) array,
4) Image DLL or EXE resource.

 

The CImageCtrl class

Now, have a quick look at CImageCtrl class, which encapsulates this behaviour:

class CImageCtrl : public CStatic
{
public:
    CImageCtrl();
    ~CImageCtrl();
    enum sizeType { SIZE_ORIGINAL, SIZE_SCALETOFIT, SIZE_CUSTOM };
    enum allignmentType { ALLIGN_TOPLEFT, ALLIGN_TOPCENTER, ALLIGN_TOPRIGHT, ALLIGN_MIDDLELEFT, 
                          ALLIGN_MIDDLECENTER, ALLIGN_MIDDLERIGHT, ALLIGN_BOTTOMLEFT, 
                          ALLIGN_BOTTOMCENTER, ALLIGN_BOTTOMRIGHT };    

    void setSizeType(int sizeType) { m_sizeType = sizeType; } // Size type: SIZE_ORIGINAL,...
    double getLeft() { return m_left; }            // x-coordinate of the image top-left point.
    double getTop() { return m_top; }              // y-coordinate of the image top-left point.
    void setWidth(double width) { m_width = width; }          // Set image width (pixels).
    double getWidth() { return m_width; }                     // Image width (pixels).
    double getWidthOriginal() { return m_widthOriginal; }     // Image original width (pixels).
    void setHeight(double height) { m_height = height; }      // Set image height (pixels).
    double getHeight() { return m_height; }                   // Image height (pixels).
    double getHeightOriginal() { return m_heightOriginal; }   // Image original height (pixels).

    void setMaintainAspectRatio(bool maintainAspectRatio) 
    { 
        m_maintainAspectRatio = maintainAspectRatio; 
    }
    double setAspectRatio(double aspectRatio) { return m_aspectRatio = aspectRatio; } 
    double getAspectRatio() { return m_aspectRatio; }         // Case "image": m_height / m_width.
    void setAllignmentType(int allignmentType) { m_allignmentType = allignmentType; }  
    void setPanMode(bool isPanMode) { m_isPanMode = isPanMode; }     // Enable/disable PAN mode.
    void setZoomMode(bool isZoomMode) { m_isZoomMode = isZoomMode; } // Enable/disable ZOOM mode.

    bool isImageShown() { return m_isImageShown > 0; } // Is image shown in the control?
    void update();                                     // Update image in the control.
    void erase();                                      // Erase image from the control.

    BOOL load(CString szFilePath);                     // Loads an image from file.
    BOOL load(IStream* piStream);                      // Loads an image from IStream interface.
    BOOL load(BYTE* pData, size_t nSize);              // Loads an image from BYTE array.
    
    // Loads image from resource.
    BOOL load(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType, WORD wlan); 
    
    BOOL convert(CString pathName, CString imageType); // Converts image to specified image type.
    
    // Loads icon from resource EXE or DLL and saves it to *.ico file.
    BOOL iconResourceToFile(CString resPathName, LPCTSTR lpName, WORD wlan, CString icoPathName); 

private:
    void release(bool calcFrameAspectRatio = true);  // Release allocated memory and initialize.

    int m_sizeType;             // Size type: SIZE_ORIGINAL, SIZE_SCALETOFILL, SIZE_CUSTOM.
    double m_left;              // x-coordinate of the image top-left point.
    double m_top;               // y-coordinate of the image top-left point.
    double m_width;             // Image width (pixels).
    double m_height;            // Image height (pixels).
    double m_widthOriginal;     // Image original width (pixels).
    double m_heightOriginal;    // Image original height (pixels).
    bool m_maintainAspectRatio; // Maintain aspect ratio or not.
    double m_aspectRatio;       // Aspect ratio factor: Case "image": m_height / m_width.
    int m_allignmentType;       // Image alignment type inside control: ALLIGN_TOPLEFT,...
    bool m_isPanMode;           // Enabled/disabled PAN mode.
    bool m_isZoomMode;          // Enabled/disabled ZOOM mode.
    double m_zoomMin;           // Minimal rectangle side value (in pixels) on ZOOM action.
    double m_zoomMax;           // Maximal rectangle side value (in pixels) on ZOOM action.
    BOOL m_isImageShown;        // Is image shown in the control?
    
    BOOL m_isInitialShow; // Initial image show? True, if not derived from PAN/ZOOM mode action.
    CPoint m_panAtPt;           // Origin point of PAN action.
    CPoint m_panOffset;         // Offset distances at PAN action.
    CPoint m_zoomAtPt;    // Point at zoom event, triggered by mouse wheel scrolling ON image.
    double m_zoomFactor;        // Zoom factor: Case > 1: zoom in, Case < 1: zoom out.
    
+   Bitmap* m_pBmp;             // Pointer to GDI+ bitmap.
+   ULONG_PTR m_gdiplusToken;   // GDI+ Token.

protected:
    virtual void PreSubclassWindow();
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
    DECLARE_MESSAGE_MAP()
};

 

PAN and ZOOM

At first, you have to enable PAN or ZOOM mode by selecting appropriate checkbox on dialog window.

Image 2 

What PAN is and how it works?

Click left mouse button, while mouse over image client area, then move mouse and finally release left mouse button. The result is image displaced by "mouse-move" vector.


What ZOOM is and how it works?

Position mouse over the image (or at least inside image client area). Use mouse middle (wheel) button to zoom in or zoom out. The result is scaled image with mouse position point fixed.

 

GDI+

The technology used for viewing images is GDI+, which was first introduced in Windows XP and Windows Server 2003 (GDI+ functionality is denoted by plus character at the start of the code line).

+


I initialized it in CimageCtrl constructor like this:

CImageCtrl::CImageCtrl(void)
    : CStatic(), m_pBmp(NULL), m_gdiplusToken(0), m_sizeType(sizeType::SIZE_SCALETOFIT), 
                 m_maintainAspectRatio(true), m_aspectRatio(1), 
                 m_allignmentType(allignmentType::ALLIGN_MIDDLECENTER), 
                 m_isPanMode(FALSE), m_isZoomMode(FALSE)
{
+   GdiplusStartupInput startupInput; GdiplusStartup(&m_gdiplusToken, &startupInput, NULL);
    
    m_isImageShown = FALSE; m_panAtPt.SetPoint(-1, -1); m_panOffset.SetPoint(0, 0); 
    m_zoomAtPt.SetPoint(-1, -1); m_zoomFactor = 1.0; m_zoomMin = 1; m_zoomMax = 99999;
}


and the clean up was made in destructor like this:

CImageCtrl::~CImageCtrl(void)
{
+   release(false); GdiplusShutdown(m_gdiplusToken);
}


The variable CImageCtrl::m_pBmp is pointer to the Bitmap, which was previously obtained by one of four CImageCtrl::load() methods, and the CImageCtrl::DrawItem() procedure does the OnPaint() "job", which is triggered by CImageCtrl::Invalidate() and CImageCtrl::UpdateWindow() methods.

void CImageCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    double w0, h0, sx, sy, s, dx, dy; CRect rect; 
    CDC *pDC = NULL, dcMem; CBitmap bmpMem, *oldBmp = NULL;

    if (pDC = GetDC())
    {
+       if (m_pBmp)
        {
            // Create an in-memory device context, compatible with the painting device context.
            dcMem.CreateCompatibleDC(pDC);        
            // Create bitmap compatible with the painting device context.
            GetClientRect(rect); bmpMem.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
            // Select the bitmap into this in-memory device context.
            oldBmp = dcMem.SelectObject(&bmpMem);  

+           Graphics graphics(dcMem.m_hDC);
            // Paint with dialog-background color.
            dcMem.FillSolidRect(rect, GetSysColor(COLOR_3DFACE)); 

            if (m_isInitialShow)
            {
                m_widthOriginal = m_pBmp->GetWidth(); m_heightOriginal = m_pBmp->GetHeight();
                m_aspectRatio = m_heightOriginal / m_widthOriginal;

                if (!m_maintainAspectRatio)
                {
                    if (m_sizeType == sizeType::SIZE_SCALETOFIT) 
                        { m_width = rect.Width(); m_height = rect.Height(); }
                    else if (m_sizeType == sizeType::SIZE_ORIGINAL) 
                        { m_width = m_widthOriginal; m_height = m_heightOriginal; }
                    m_left = m_top = 0;
                }
                else
                {
                    if (m_sizeType == sizeType::SIZE_SCALETOFIT)
                    {
                        sx = rect.Width() / m_widthOriginal; 
                        sy = rect.Height() / m_heightOriginal;
                        s = (sx > sy) ? sy : sx; 
                        m_height = m_aspectRatio * (m_width = s * m_widthOriginal);
                    }
                    else if (m_sizeType == sizeType::SIZE_CUSTOM)
                    {
                        sx = m_width / m_widthOriginal; 
                        sy = m_height / m_heightOriginal;
                        s = (sx > sy) ? sy : sx; 
                        m_height = m_aspectRatio * (m_width = s * m_widthOriginal);
                    }
                    else if (m_sizeType == sizeType::SIZE_ORIGINAL) 
                        { m_width = m_widthOriginal; m_height = m_heightOriginal; }
                }

                if (m_allignmentType == allignmentType::ALLIGN_TOPLEFT) m_left = m_top = 0;
                else if (m_allignmentType == allignmentType::ALLIGN_TOPCENTER) 
                    { m_left = (rect.Width() - m_width) / 2; m_top = 0; }
                else if (m_allignmentType == allignmentType::ALLIGN_TOPRIGHT) 
                    { m_left = rect.Width() - m_width; m_top = 0; }
                else if (m_allignmentType == allignmentType::ALLIGN_MIDDLELEFT) 
                    { m_left = 0; m_top = (rect.Height() - m_height) / 2; }
                else if (m_allignmentType == allignmentType::ALLIGN_MIDDLECENTER) 
                { 
                    m_left = (rect.Width() - m_width) / 2; m_top = (rect.Height() - m_height) / 2;
                }
                else if (m_allignmentType == allignmentType::ALLIGN_MIDDLERIGHT) 
                    { m_left = rect.Width() - m_width; m_top = (rect.Height() - m_height) / 2; }
                else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMLEFT) 
                    { m_left = 0; m_top = rect.Height() - m_height; }
                else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMCENTER) 
                    { m_left = (rect.Width() - m_width) / 2; m_top = rect.Height() - m_height; }
                else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMRIGHT) 
                    { m_left = rect.Width() - m_width; m_top = rect.Height() - m_height; }
            }
            else if (m_zoomAtPt.x < 0) { m_left += m_panOffset.x; m_top += m_panOffset.y; }// PAN
            else if (m_zoomFactor > 1e-6)                                                  // ZOOM
            {
                ScreenToClient(&m_zoomAtPt);
                if ((dx = (m_zoomAtPt.x - m_left)) < 1e-6) { m_zoomAtPt.x = (LONG)m_left;dx = 0; }
                else if (m_zoomAtPt.x > m_left + m_width - 1e-6) 
                    { m_zoomAtPt.x = (LONG)(m_left + (dx = m_width)); }
                if ((dy = (m_zoomAtPt.y - m_top)) < 1e-6) { m_zoomAtPt.y = (LONG)m_top; dy = 0; }
                else if (m_zoomAtPt.y > m_top + m_height - 1e-6) 
                    { m_zoomAtPt.y = (LONG)(m_top + (dy = m_height)); }

                w0 = m_width * m_zoomFactor; h0 = m_height * m_zoomFactor;
                if (w0 >= m_zoomMin && w0 <= m_zoomMax && h0 >= m_zoomMin && h0 <= m_zoomMax) 
                {
                    dx *= (w0 / m_width); dy *= (h0 / m_height);
                    m_left = m_zoomAtPt.x - dx; m_top = m_zoomAtPt.y - dy; 
                    m_height = m_aspectRatio * (m_width = w0);
                    GetParent()->SendMessage(WM_APP + 1, 12345, 0); 
                }
            }

+           m_isImageShown = (graphics.DrawImage(m_pBmp, 
                             (int)(m_left + 1e-6), (int)(m_top + 1e-6), 
                             (int)(m_width + 1e-6), (int)(m_height + 1e-6)) == Status::Ok);
            pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY);
            dcMem.SelectObject(oldBmp); bmpMem.DeleteObject(); dcMem.DeleteDC();
        }
        else 
        { 
            GetClientRect(rect); pDC->FillSolidRect(rect, GetSysColor(COLOR_3DFACE));
            m_isImageShown = FALSE; 
        }
        ReleaseDC(pDC); m_zoomAtPt.SetPoint(-1, -1);
    }
}

 

GDI: In-memory Device Context

This technique was used to eliminate "flickering" effect during PAN or ZOOM action. The trick is to create in-memory device context (which is compatible with painting device context), where all the painting is done. At the end, you just copy a portion of in-memory device context to the painting device context. This was achieved by following statements.

CDC *pDC = NULL, dcMem; CBitmap bmpMem, *oldBmp = NULL;

pDC = GetDC();

// Create an in-memory device context, compatible with the painting device context.
dcMem.CreateCompatibleDC(pDC);  

// Create bitmap compatible with the painting device context.
GetClientRect(rect); bmpMem.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());

// Select the bitmap into this in-memory device context.
oldBmp = dcMem.SelectObject(&bmpMem);                                                

// Do all the paintings with dcMem in-memory device context here.

// Copy a portion of the in-memory device context to the painting device context.
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY); 

dcMem.SelectObject(oldBmp); bmpMem.DeleteObject(); dcMem.DeleteDC(); ReleaseDC(pDC);

If you would like to learn more about working with device contexts, here 2 is an excellent article by Marius Bancila.
 

Resource-Only DLL

I have decided to create a resource-only DLL3 (containing all images) in order to test "Load image from Resource DLL or EXE" option.

Resource-only DLL is a  DLL that contains nothing but resources, such as images, strings, dialog boxes,...


How can you create it in Microsoft Visual Studio?

1) Create "New Project->WIn32 Project->DLL" application type.
2)
Select "Project->Properties->Configuration Properties->Linker->Advanced->No Entry Point" and set option
    "Yes (/NOENTRY)".
3) Add resources.


I added images of type BMP, DIB, JPG, JPEG, JPE, JFIF, GIF, TIF, TIFF, PNG, ICO, WMF, EMF:

*.ico image under "Icon" resource type,
*.bmp images under "Bitmap" and "RCDATA" resource type,
*.jpg image under "JPG" custom resource type,
*.png image under "PNG" custom resource type, etc.

 

ResourceList module

ResourceList module, containing "ResourceList.h" and "ResourceList.cpp" (project ImageControl), was implemented to list ALL resources, defined by resource language, resource type and resource name. I would like to highlight, that resource type and name could be string or integer string (for example MAKEINTRESOURCE(153), RT_BITMAP).

And how to get resources that might be image-type?

#include "ResourceList.h"

CResourceList rl; HMODULE hModule;
CArray<tResourceLCID*> *pRes;      // Array of resource "Locale Culture Identifier"-s.

if(hModule = LoadLibrary(_T("...")))
{
   rl.setResourceType(CResourceList::resourceType::RESOURCE_IMAGE);
   pRes = rl.getResourceList(hModule); // Get the resource list. Do something with it.

   FreeLibrary(hModule);
}

 

Icon module

Icon module, containing "Icon.h" and "Icon.cpp" (project ImageControl), was developed to provide importIcon() (from resource module or *.ico file) and exportIcon() (to *.ico file) methods.

And how to load resource icon and export it to *.ico file?

#include "Icon.h"

CIconExtractor ie; HMODULE hModule;
LPCTSTR lpName = ...;                     // Icon resource name.
WORD wlan = ...;                          // Icon resource language.
CString icoPathName = _T("...");          // Output icon path.

// You can get resource list as mentioned in previous section, and filter only 
// RT_GROUP_ICON resources by calling setResourceType(RT_GROUP_ICON) preliminary.
// Thus you will get all resource icons listed.

if(hModule = LoadLibrary(_T("...")))
{
   if(ie.importIcon(hModule, lpName, wlan)) ie.exportIcon(icoPathName);

   FreeLibrary(hModule);
}

 

Load image from a resource DLL or EXE

Using CImageCtrl class, you can accomplish that in 3 steps.

1) Load appropriate resource dll or exe by calling function hModule = LoadLibrary().
2) Call m_image.load(hModule, MAKEINTRESOURCE(.), ., .).
3) Unload the resource by calling FreeLibrary(hModule).

This is realized in CImageControlDlg::OnCbnSelchangeComboResname() event.

C++
void CImageControlDlg::OnCbnSelchangeComboResname()
{
    int i, j, k; bool isTypeInt, isNameInt; CString type, name, buff; HMODULE hModule;

    if ((i = ((CComboBox *)GetDlgItem(IDC_COMBO_RESLOCALE))->GetCurSel()) != CB_ERR 
        && (j = ((CComboBox *)GetDlgItem(IDC_COMBO_RESTYPE))->GetCurSel()) != CB_ERR 
        && (k = ((CComboBox *)GetDlgItem(IDC_COMBO_RESNAME))->GetCurSel()) != CB_ERR)
        if (hModule = LoadLibrary(m_resFilePath))
        {
            isTypeInt = m_pRes->GetAt(i)->resTypes.GetAt(j)->isInteger; 
            type = m_pRes->GetAt(i)->resTypes.GetAt(j)->type;
            isNameInt = m_pRes->GetAt(i)->resTypes.GetAt(j)->resNames.GetAt(k)->isInteger; 
            name = m_pRes->GetAt(i)->resTypes.GetAt(j)->resNames.GetAt(k)->name;

      // if(m_image.load(m_hModule, MAKEINTRESOURCE(164), RT_GROUP_ICON, m_pRes->GetAt(i)->lcid)) 
      // if(m_image.load(m_hModule, MAKEINTRESOURCE(153), _T("GIF"), m_pRes->GetAt(i)->lcid)) 
            if(m_image.load(hModule, isNameInt ? MAKEINTRESOURCE(_wtoi(name)) : name,
                        isTypeInt ? MAKEINTRESOURCE(_wtoi(type)) : type, m_pRes->GetAt(i)->lcid))
            {
                m_isOnEnChangeEditWidthHeight = false;
                buff.Format(_T("%d"), m_image.getWidth()); 
                GetDlgItem(IDC_EDIT_WIDTH)->SetWindowText(buff);
                buff.Format(_T("%d"), m_image.getHeight()); 
                GetDlgItem(IDC_EDIT_HEIGHT)->SetWindowText(buff);
                m_isOnEnChangeEditWidthHeight = true;

                ((CComboBox*)GetDlgItem(IDC_COMBO_RESTYPE))->GetLBText(j, buff);
            }
            FreeLibrary(hModule);
        }

    GetDlgItem(IDC_BUTTON_SAVEICONAS)->EnableWindow(buff == _T("Icon"));
    GetDlgItem(IDC_BUTTON_SAVEAS)->EnableWindow(m_image.isImageShown());
}

Remark:

Pay attention on loading icon. The resource type is RT_GROUP_ICON. Icon resource can contain multiple images, each of them of type RT_ICON.

 

Now, let us have a look at CImageCtrl::load(hModule, MAKEINTRESOURCE(.), ., .) method more precisely. Basically, the code is divided into two branches based on resource type (non-icon and icon resource).

1) "Non-icon resource" passes this code snippet.

BOOL CImageCtrl::load(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType, WORD wlan)
{
    HRSRC hRes; DWORD resSize; HGLOBAL hGlobal, hGlobal2, hGlobal3; HICON hIcon;
    void *pRes = NULL, *pRes2 = NULL; IStream *pStream = NULL; GRPICONDIR *pIconDir = NULL;
    
    release(); m_isImageFromResource = true;
    if ((hRes = FindResourceEx(hModule, lpType, lpName, wlan)) 
       && (resSize = SizeofResource(hModule, hRes)) && (hGlobal = LoadResource(hModule, hRes)))
    {
         if (lpType != RT_GROUP_ICON)
         {
             if (lpType == RT_BITMAP)
             {
               m_pBmp = new Bitmap((HBITMAP)LoadImage(hModule, lpName, IMAGE_BITMAP, 0, 0, 0), 0);
               m_isInitialShow = TRUE; Invalidate(); UpdateWindow(); m_isInitialShow = FALSE; 
             }
             else if (pRes = LockResource(hGlobal))
             {
                 if (hGlobal2 = GlobalAlloc(GMEM_MOVEABLE, resSize))
                 {
                     if (pRes2 = GlobalLock(hGlobal2))
                     {
                         CopyMemory(pRes2, pRes, resSize);
                         if (CreateStreamOnHGlobal(hGlobal2, FALSE, &pStream) == S_OK)  
                         { 
                             if (pBmp = Bitmap::FromStream(pStream))
                             {
                      Graphics g(m_pBmp = new Bitmap(pBmp->GetWidth(), pBmp->GetHeight()));
                      g.DrawImage(pBmp, 0, 0, pBmp->GetWidth(), pBmp->GetHeight()); delete pBmp;
                             }
                             pStream->Release();  
                         }
                         UnlockResource(hGlobal2);
                     }
                     GlobalFree(hGlobal2);
                 }
                 UnlockResource(hGlobal);
             }
         }
         FreeResource(hGlobal);
     }
     
     m_isImageFromResource = true; return m_isImageShown;
}
 
2) "Icon resource" is more complicated.

Here4 you can find out, that icon resource (RT_GROUP_ICON) is simply GRPICONDIR structure, defined below.
typedef struct
{
    WORD            idReserved;   // Reserved (must be 0).
    WORD            idType;       // Resource type (1 for icons).
    WORD            idCount;      // Number of icon images.
    GRPICONDIRENTRY idEntries[1]; // An entry for each icon image.
} GRPICONDIR, *LPGRPICONDIR;

Meanwhile, all icon images, which are of type RT_ICON, are located in the array of GRPICONDIRENTRY structures.
typedef struct
{
    BYTE    bWidth;               // Width (in pixels) of the image.
    BYTE    bHeight;              // Height (in pixels )of the image.
    BYTE    bColorCount;          // Number of colors in image (0 if >= 8bpp).
    BYTE    bReserved;            // Reserved (must be 0).
    WORD    wPlanes;              // Color Planes.
    WORD    wBitCount;            // Bits per pixel.
    DWORD   dwBytesInRes;         // How many bytes in this resource?
    WORD    nID;                  // The ID.
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
 
In "code language" that would require these additional code lines.
GRPICONDIR *pIconDir = NULL;

if (pIconDir = (GRPICONDIR*)LockResource(hGlobal))
{
    if (pIconDir->idCount && 
       (hRes = FindResource(hModule, MAKEINTRESOURCE(pIconDir->idEntries[0].nID), RT_ICON)) &&
       (resSize = SizeofResource(hModule, hRes)) && (hGlobal3 = LoadResource(hModule, hRes)))
    {
       //
       // Same as in non-icon resource images.
       //

       FreeResource(hGlobal3);
    }
    UnlockResource(hGlobal);
}

 

Resizable Dialog

We would like to define appropriate actions, when dialog is resized, like repositioning and/or resizing dialog controls. For this purposes "StandardLibrary" (static library project) with module "ResizableDlg" was developed.

One approach is to describe these actions relatively to dialog size change from anchor point. If we would like to perform only repositions, then one anchor point would be enough to describe all possible repositions (let it be top-left dialog point). Resize action is more tricky. If we observe resize in x-direction, it could be done with left edge fixed and right edge moved or vice versa. There is similar case with resize in y-direction. But with anchor point top-left,the corresponding resize in x-direction would be to move right edge and in y-direction would be to move bottom edge. Similary we can define resize actions in both directions for remaining three anchor points: bottom-left, top-right, bottom-right.

Image 3

There are four basic actions relative to dialog size change:

  • repositioning in x-direction (parameter dxMoveRelative),
  • repositioning in y-direction (parameter dyMoveRelative),
  • resize in x-direction (parameter dxResizeRelative),
  • resize in y-direction (parameter dyResizeRelative).

All four actions can be described by a parameter from interval [0,1]. 0 means no action, 1 means action in x- or y-direction for the same ammount as dialog's x- or y-direction change, 0.3 means action in x- or y-direction for the 30% of the ammount ...

It is a good idea to be
dxMoveRelative + dxResizeRelative <= 1 and  dyMoveRelative + dyResizeRelative <= 1,
otherwise the control could leave the dialog window area, which is not supposed to happen.

If we choose all four parameters to be zero, then control is anchored at fixed x- and y-distance from anchor point.

Finally, there is a method CResizableDlg::moveResizeControl(...) in "ResizableDlg" module with parameters:

    int nID (control ID),
    int anchor (anchor point: TOP_LEFT, BOTTOM_LEFT, TOP_RIGHT, BOTTOM_RIGHT),
    double dxMoveRelative (repositioning in x-direction),
    double dyMoveRelative (repositioning in y-direction),
    double dxResizeRelative (resize in x-direction),
    double dyResizeRelative (resize in y-direction),
    bool isTransparent (true for group boxes),

which defines actions to perform on control at dialog resize.

If you would like to use ResizableDlg module, proceed here.

 

Background

  • GDI+ (Graphics, Bitmap, Metafile)
  • GDI (In-memory Device Context, Client Device Context)
  • Resource-Only DLL
  • Resizable Dialog

 

References

1. Picture Control - Tobias Eiseler - CodeProject
2. Working with Device Contexts - Marius Bancila - CodeGuru
3. Creating Resource-Only DLL - Microsoft - MSDN
4. Icons - John Hornick - MSDN
 

Using the code


File "Source.zip" contains "ImageControl" static library project.


You can use this library in your project as follows.

1) Under "Project->Properties->Configuration Properties->C/C++->General->Additional Include Directories"
    add appropriate relative path "..\ImageControl\inc" , targeting "ImageCtrl.h", "ResourceList.h" and "Icon.h" file.

2)  Under "Project->Properties->Configuration Properties->Linker->General->Additional Library Directories"
    add appropriate relative path "..\ImageControl\x64\Release", targeting "ImageControl.lib" file.

3)  Under "Project->Properties->Configuration Properties->Linker->Input->Additional Dependencies"
    add "ImageControl.lib" file.

4)

     4.1) If you would like to use ImageCtrl module ...

          4.1.1) Include header file "ImageCtrl.h" and declare a variable of type CImageCtrl in your dialog class.

C++
#include "ImageCtrl.h"

class CImageControlDlg : public CDialogEx
{
public:
    CImageControlDlg(CWnd* pParent = NULL);    // standard constructor
    enum { IDD = IDD_IMAGECONTROL_DIALOG };

private:
    CImageCtrl m_image;
    HICON m_hIcon;

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    virtual BOOL OnInitDialog();
    afx_msg void OnCbnSelchangeComboLoadOption();
    afx_msg void OnCbnSelchangeComboResize();
    afx_msg void OnBnClickedButtonLoadimage();
    afx_msg void OnBnClickedButtonAbout();
    DECLARE_MESSAGE_MAP()    
};

          4.1.2) Subclass your CStatic control, which will hold the image.

void CImageControlDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATIC, m_image);
}

          4.1.3) Call one of four overloaded methods m_image.load() to load image into control.

     4.2) If you would like to use only ResourceList module, proceed here.

     4.3) If you would like to use only Icon module, proceed here.

 


File "Source.zip" contains "StandardLibrary" static library project.


 

You can use this library in your project as follows.

1) Under "Project->Properties->Configuration Properties->C/C++->General->Additional Include Directories"
    add appropriate relative path "..\StandardLibrary\inc" , targeting "ResizableDlg.h".

2)  Under "Project->Properties->Configuration Properties->Linker->General->Additional Library Directories"
    add appropriate relative path "..\StandardLibrary\x64\Release", targeting "StandardLibrary.lib" file.

3)  Under "Project->Properties->Configuration Properties->Linker->Input->Additional Dependencies"
    add "StandardLibrary.lib" file.

 4) Include header file "ResizableDlg.h" and derive your dialog class from CResizableDlg.

C++
#include "ImageCtrl.h" 
#include "ResizableDlg.h"

class CImageControlDlg : public CResizableDlg
{
public:
    CImageControlDlg(CWnd* pParent = NULL);    // standard constructor
    enum { IDD = IDD_IMAGECONTROL_DIALOG };

private:
    CImageCtrl m_image;
    HICON m_hIcon;

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    virtual BOOL OnInitDialog();
    afx_msg void OnCbnSelchangeComboLoadOption();
    afx_msg void OnCbnSelchangeComboResize();
    afx_msg void OnBnClickedButtonLoadimage();
    afx_msg void OnBnClickedButtonAbout();
    DECLARE_MESSAGE_MAP()    
};

5) Define constructor like this:

CImageControlDlg::CImageControlDlg(CWnd* pParent /*=NULL*/)
    : CResizableDlg(CImageControlDlg::IDD, 400, 490, pParent), m_pRes(NULL), m_isOnEnChangeEditWidthHeight(true)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON_OCEANWAVE); m_rl.setResourceType(CResourceList::resourceType::RESOURCE_IMAGE); // m_rl.setResourceType((UINT)RT_GROUP_ICON);
}

where 400 means minimal dialog width and 490 minimal dialog height. If any of these parameters is 0, the minimal dialog width/height is set to initial dialog width/height values.

6) Add method CResizableDlg::moveResizeControl(...) for each dialog control, which should be repositioned and/or resized, at the beginning of OnInitDialog() and call CResizableDlg::OnInitDialog() afterwards.

BOOL CImageControlDlg::OnInitDialog()
{
    moveResizeControl(IDC_STATIC_IMAGEFRAME, tControl::anchor::TOP_LEFT, 0, 0, 1, 1);
    moveResizeControl(IDC_STATIC_LOADOPTION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_LOADOPTION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_GROUP_RESOURCE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0, true);
    moveResizeControl(IDC_STATIC_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_EDIT_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_BUTTON_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_RESLOCALE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_RESLOCALE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_RESTYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_RESTYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_RESNAME, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_RESNAME, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_BUTTON_LOADIMAGE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_GDIPLUS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_GROUP_SIZEPOSITION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0, true);
    moveResizeControl(IDC_STATIC_SIZETYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_SIZETYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_WIDTH, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_EDIT_WIDTH, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_WIDTHUNITS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_HEIGHT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_EDIT_HEIGHT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_HEIGHTUNITS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_CHECK_MAR, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_STATIC_ALIGNMENT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_COMBO_ALIGNMENT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_CHECK_PAN, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_CHECK_ZOOM, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_BUTTON_SAVEICONAS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDC_BUTTON_SAVEAS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0);
    moveResizeControl(IDOK, tControl::anchor::TOP_LEFT, 1, 1, 0, 0);
    moveResizeControl(IDC_BUTTON_ABOUT, tControl::anchor::TOP_LEFT, 1, 1, 0, 0);

    CResizableDlg::OnInitDialog();

    return TRUE;
}

 


File "Demo.zip" contains 4 projects:

  • [LIB]/ImageControl (static library project),
  • [LIB]/StandardLibrary (static library project),
  • [Resource]/ImageControlRes (resource-only DLL, containing all images),
  • ImageControl Demo (demonstrative project).

 How to use it?

1) Unzip "Demo.zip" file.
2) Build all four projects.
3) Run the application "ImageControl Demo.exe". Remember that all image files are located under "[Resource]/ImageControlRes/res" folder and in resource-only "ImageControlRes.dll" under "[Resource]/ImageControlRes/x64/Release".
 

History

Version 1.5: Released on 2016-06-18

New additional feature:

  • Resizable dialog support with minimal dialog window size defined and last window placement remembered.

          Added "StandardLibrary" (static library project), which supports repositioning and/or resizing dialog controls
          on dialog resize. CImageControlDlg is now derived from CResizableDlg (<s>CDialogEx)</s> in order to
          support  these resizable dialog features.
 

Version 1.4: Released on 2015-11-12

  • Image object m_pImage from CImageCtrl class has been found redundant and therefore removed, meanwhile Bitmap object m_pBmp took over its place.

New additional features:

  • Create custom-sized 32-bit icon from any image,
  • Export resource images,
  • PAN and ZOOM resource images.
     

Version 1.3: Released on 2015-10-28

  • Zoom operation has been redefined with higher rate of accurancy. Now, it is also possible to execute it, if mouse positioned outside image rectangle (but inside image client area).


Version 1.2: Released on 2015-10-24

New additional features:

  • Import "Bitmap" resource,
  • Import images from arbitrary multi-language (resource) dll or exe,
  • Custom Resize,
  • Pan,
  • Zoom,
  • Export Image,
  • Extract Resource Icon.
     

Version 1.1  Released on 2015-10-08

  • Image object image from CImageCtrl::DrawItem() has been replaced by m_pImage pointer to Image and  moved to CImageCtrl class. Now it is initialized only once in each image cycle - no matter how many times CImageCtrl::DrawItem() is called!
  • Function load(CString szFilePath) has been optimized by removing Read()/Write() calls.
  • Function load(IStream* piStream) has been optimized by removing  IStream-copy.                     


Version 1.0: Released on 2015-10-06

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Patrik Mlekuž
Slovenia Slovenia
No Biography provided

Comments and Discussions

 
SuggestionWould you please add a function of ROI choosing? Pin
grave02161-Mar-21 5:22
Membergrave02161-Mar-21 5:22 
QuestionMulti Monitor "Sensitivity" Pin
Dimitrios Fountoukidis9-Feb-21 11:48
MemberDimitrios Fountoukidis9-Feb-21 11:48 
QuestionMessage Removed Pin
29-Sep-20 9:39
MemberMember 1495057529-Sep-20 9:39 
QuestionMessage Removed Pin
28-Sep-20 10:46
MemberMember 1495057528-Sep-20 10:46 
Questionimagectrl.cpp(89): error C2660: 'Gdiplus::GdiplusBase::operator new': function does not take 3 arguments Pin
Member 1243736925-Jul-19 22:21
MemberMember 1243736925-Jul-19 22:21 
QuestionCoding Style - my 2 cents Pin
real_skydiver3-Apr-19 9:38
Memberreal_skydiver3-Apr-19 9:38 
QuestionFlickering issue Pin
Varalakshmi Chandran19-Jan-19 15:20
MemberVaralakshmi Chandran19-Jan-19 15:20 
PraiseCoding Style Pin
Rick York2-Nov-17 10:50
mveRick York2-Nov-17 10:50 
QuestionMy Vote if Five. But Need to know :Mouse move and Mouse wheel Messages Pin
thelvaci18-Jun-17 11:04
Memberthelvaci18-Jun-17 11:04 
QuestionI love it! It is the just thing what I want. Thank you! Pin
Member 1021573622-Oct-16 18:26
MemberMember 1021573622-Oct-16 18:26 
PraiseI really like it ! Pin
_Flaviu17-Nov-15 21:16
Member_Flaviu17-Nov-15 21:16 
GeneralRe: I really like it ! Pin
Patrik Mlekuž17-Nov-15 23:12
MemberPatrik Mlekuž17-Nov-15 23:12 
GeneralMy vote of 5 Pin
Farhad Reza8-Oct-15 2:23
MemberFarhad Reza8-Oct-15 2:23 
GeneralRe: My vote of 5 Pin
Patrik Mlekuž8-Oct-15 2:32
MemberPatrik Mlekuž8-Oct-15 2:32 
SuggestionShould have just create the image object once Pin
Shao Voon Wong6-Oct-15 17:51
mvaShao Voon Wong6-Oct-15 17:51 
GeneralRe: Should have just create the image object once Pin
Patrik Mlekuž7-Oct-15 3:19
MemberPatrik Mlekuž7-Oct-15 3:19 
GeneralRe: Should have just create the image object once Pin
Patrik Mlekuž7-Oct-15 14:59
MemberPatrik Mlekuž7-Oct-15 14:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.