Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

PicZoom: A Photo Viewer Created in OpenGL

4.86/5 (64 votes)
16 Feb 2011CPOL16 min read 166K   11.6K  
PicZoom: A Photo Viewer created in OpenGL

Table of Contents

Introduction

This article discuss implementation details of PicZoom application [A photo viewer with some functionalities are similar to the Picasa Photo Viewer]. PicZoom is an MFC Dialog based application, and OpenGL is used for drawing. Shader programs are not used for display, and therefore I hope PicZoom will run without a high-end graphics card. Since it requires some graphics memory to prepare all textures [Background, Image, Buttons, etc], graphics card is required to run PicZoom. Limitations section explains graphics memory limitation of PicZoom.

Background

When I started studying OpenGL, I created a simple application to load an image from file and do simple rotation, zoom with it. OpenGL provides APIs to do some simple image operations such as zoom, pan, rotate. Picasa Photo Viewer is a simple and superb image browser tool. In this article, I am just imitating some functionalities of Picasa Photo Viewer with OpenGL and MFC. I’m sure performance of this application is not satisfying with Picasa, but it’s an attempt to do something with OpenGL and MFC. Any comments and improvement suggestions are welcome.

ScreenShot.gif

Screenshot of PicZoom [With some image operations].

SreenShot_Dlg.jpg

Screenshot of PicZoom in dialog mode.

Using the Code

Initially, I did almost all functionalities of PicZoom in a single class, CPicZoomDlg. It was very difficult to manage everything in a single class. Therefore, I prepared different classes based on different functionalities. For example, ImageArea class will handle all operations [draw, zoom, pan, mouse cursor based on image] related to Image. Then PicZoomDlg will create objects of ImageArea, Background, etc., and manage them with less effort. And I prepared opengl wrapper classes, to handle initialization, texture creation, text drawing, circle drawing, etc.

When starting PicZoom, it captures the desktop background and creates a semitransparent view to the background. The implementation details are as follows:

FourComponents.jpg

The screenshot of PicZoom with semi-transparent desktop as background.

How to Capture Screenshot of Desktop

Just call ::GetDesktopWindow() and retrieve desktop window handle. Then prepare a memory dc and read contents [RGB data] of desktop window. GetDIBits()provides RGB buffer of desktop window to an allocated memory. Here is the code to capture screenshot of desktop.

C++
bool ScreenCapture::TakeScreenShot()
{
    // Get Desktop window.
    HWND hDesktop = ::GetDesktopWindow();

    // Get temporary Dc for getting data from Desktop.
    CDC dc;
    HDC hdc = ::GetWindowDC(hDesktop);
    dc.Attach(hdc);

    CDC MemoryDC;
    MemoryDC.CreateCompatibleDC(&dc);

    CBitmap BmpObj;
    BmpObj.CreateCompatibleBitmap(&dc, sz.cx, sz.cy);
    CBitmap * oldbm = MemoryDC.SelectObject(&BmpObj);
    // Get data from Desktop to Bitmap.
    MemoryDC.BitBlt(0, 0, sz.cx, sz.cy, &dc, 0, 0, SRCCOPY);

   // Read RGB data of Desktop window Dc.
   int nStatus = ::GetDIBits( MemoryDC.m_hDC, (HBITMAP)BmpObj.m_hObject, 0, sz.cy,
        m_pBuffer, &stBitmapInfo,DIB_RGB_COLORS );
} 

Main Components

The above figure shows the split up of main classes used in PicZoom. PicZoom creates a MFC dialog, and the painting of client area is handled with OpenGL.

Initially, PicZoomDlg creates 4 main components of PicZoom, [Background, ImageArea, BottomWindows, and CloseButton] and adds to a vector named WindowList. Whenever we get WM_PAINT in dialog, Draw() commands are passed to all the 4 components. Like <code>Draw(), all mouse messages are also passed to the components.

Functionalities and Implementation of 4 components.

All these classes [Background, ImageArea, BottomWindows, and CloseButton] are derived from GLWindowBase, the base class designed for handling different windows( or image components).

1. Background

This class is responsible to create one texture with desktop image. The RGB buffer with screenshot of desktop is obtained with ScreenCapture class. The background image texture is created with 25% transparency. It is achieved with glPixelTransferf() before texture loading. glPixelTransferf is used to provide scale factor of Red, Green Blue channels.

C++
// Set Pixel Transfer to make semitransparent effect of Desktop.
glPixelTransferf( GL_RED_SCALE, 0.75 );
glPixelTransferf( GL_GREEN_SCALE, 0.75 );
glPixelTransferf( GL_BLUE_SCALE, 0.75 );
// Create Desktop Texture.
if( !m_pImage->Create( m_nWidth, m_nHeight, pbyScreenCapuredData ))
{
    AfxMessageBox(L"Texture Creation failed.");
    return false;
}

glPixelTransferf( GL_RED_SCALE, 1.0 );
glPixelTransferf( GL_GREEN_SCALE, 1.0 );
glPixelTransferf( GL_BLUE_SCALE, 1.0 );
The Draw() handler just displaying this texture to the entire screen. When switching from Desktop mode to Dialog mode, the Background instance is deleted from the WindowList and the background drawing is avoided.
C++
bool BackGround::Draw()
{
    if(  !GLWindowBase::m_bVisible )
    {
        return true;
    }
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    // Here draws the background image.
    m_pImage->Enable();
    m_pVertexBuffer->DrawVertexBuffer( GL_POLYGON );
    return true;
}  
2. ImageArea

The display and all operations (Zoom, Translation, Rotation) of currently loaded image file is handled in this class.

ImageArea creates one opengl texture with RGB data of the image file. Whenever user requests a new image file, ImageArea creates the RGB buffer of new file with BMPLoader class. BMPLoader class uses GdiPlus for creating RGB buffer of image file of type *.jpg, *.bmp, *.png, and *.tga. Whenever Draw() receives, ImageArea will display this texture with zoom, translation, and rotation. The details of Zoom, Translation and Rotation are explained below.

2. 1. Zoom

Zoom functionality is simply achieved with OpenGL scale feature. glScalef() is called before displaying the texture, and therefore the texture will be scaled based on the current zoom factor.

Whenever mouse wheel received in Dialog, Dialog will send it to all components, and therefore ImageArea will receive mouse wheel message. The zoom factor is calculated based on the current scroll value.

C++
bool ImageArea::OnMouseWheel( const int nX_I, const int nY_i,
	const UINT nFlags, const short zDelta )
{
    float fZoomValue = float( zDelta ) / WHEEL_DELTA;

    if( 0 == m_fZoomOffset )
    {
        ::SetTimer( m_hParentWindow, TIMER_ZOOM, 5, 0 );
    }

    // Find out zoom factor based on width of and height of image.
    if( m_nImageWidth > m_nWidth || m_nImageHeight > m_nHeight )
    {
        float fImageToDesktopRatioX = (float)m_nWidth / m_nImageWidth;
        float fImageToDesktopRatioY = (float)m_nHeight / m_nImageHeight;
        float fImageToDesktopRatio =
		min( fImageToDesktopRatioY, fImageToDesktopRatioX );
        fZoomValue = fZoomValue * fImageToDesktopRatio;
    }

    m_fZoomOffset += ( fZoomValue / 100 );
    // Apply Zoom factor 15 times. then first single scroll make 15% zoom.
    m_ZoomTimer.SetMaxElapseTime( 15 );
    return true;
} 

Inside ImageArea::Draw(), Zoom factor is added to current zoom factor, and then calls glScalef( m_fZoom, m_fZoom, 1.0 ), scaling is not applied in Z order, since it is not required.

2. 2. Display of Zoom factor

Whenever Zoom Factor is changing, ZoomText class will display the new Zoom value. ZoomText simply draws a rounded rect and displays the current zoom factor with text drawing of OpenGL. FontEngine class is created to handle the drawing of text.

FontEngine creates a display list with bitmap of all characters. This can be simply achieved through wglUseFontBitmaps() function of OpenGL.

C++
bool FontEngine::Create( HDC hDeviceContext_i )
{
    VERIFY(m_pFont->CreateFont(
        15, // nHeight
        7, // nWidth
        0, // nEscapement
        0, // nOrientation
        FW_BOLD, // nWeight
        FALSE, // bItalic
        FALSE, // bUnderline
        0, // cStrikeOut
        ANSI_CHARSET, // nCharSet
        OUT_DEFAULT_PRECIS, // nOutPrecision
        CLIP_DEFAULT_PRECIS, // nClipPrecision
        ANTIALIASED_QUALITY, // nQuality
        DEFAULT_PITCH, // nPitchAndFamily
        L"Arial")); // lpszFacename

    HGDIOBJ hOldFont = ::SelectObject(hDeviceContext_i, m_pFont->m_hObject);

    if( !wglUseFontBitmaps( hDeviceContext_i, 0, 256 * 2, 1000 ))
    {
        return false;
    }

    ::SelectObject( hDeviceContext_i, hOldFont );

    return true;
}

ZoomText.jpg

Screenshot of Zoom Text display.

Zoom Text displayed at center of dialog, with current zoom factor. This text is displayed in a semi transparent way. Hide and Show are very smooth, which is implemented by blending feature of OpenGL. When drawing new object, new object and old object[already drawn object] are combined and create a transparent look of new object. Alpha value is started from 0.0, and increased 0.1 during each frame, and get a smooth appearance. When hidden, Alpha value is decreased from 1.0 to 0.0, and gets a smooth disappearance effect.

2. 3. Translation

LButtonDown and MouseMove messages are tracked in ImageArea, and find out the translation required in X and Y direction.When drawing the image texture, ImageArea::Draw() will apply translation with OpenGL translate function glTranslatef.

C++
// Y value is -ve, only because opengl Y coordinate is
//increasing from bottom to top,
// But Y direction mouse movement received in Dialog is decreasing from bottom to top.
glTranslatef( m_fXTranslation, -m_fYTranslation, 0.0 );
2. 4. Rotation

Rotation is simply implemented with glRotate() function. The rotation angle is calculated based on the current rotation state. Possible rotation values are listed below:

C++
const float ROTATION_ANGLES[4] =
{ 0.0,// No rotation
  270.0, // First Clockwise
  180.0, // Second Clockwise
  90.0 // 3rd clockwise
}; 

Inside ImageArea::Draw(), rotation angle is applied to z-order to achieve the required rotation.

C++
/// Drawing of Image.
bool ImageArea::Draw()
{
    // ....................
    // Apply rotation
    glRotatef( ROTATION_ANGLES[m_nRotate], 0, 0, 1 );

    m_pVertexBuffer->DrawVertexBuffer( GL_QUADS );
   // ...........................
    return true;
} 
2. 5. Vertex buffer logic

Here we can discuss the vertex buffer logic. The below picture shows the 4 corners of image and its corresponding vertex coordinate. Rotation is applied to this vertex buffer and creates a rotated image display.

Rotation_No.jpg

Image is displayed to screen using GLVertexBuffer class. GLVertexBuffer is a wrapper class for drawing any vertex with texture coordinate. glInterleavedArrays() is called to draw one object to screen. In ImageArea class, GLVertexBuffer object is created with 4 vertices to draw the image into screen.

C++
// Vertex buffer creation logic in ImageArea.
bool ImageArea::SetupWindow()
{
    // Setup Vertex buffer and UnProject.
    int nHalfOfImageY = m_nImageHeight / 2;
    int nHalfOfImageX = m_nImageWidth / 2;
    /*
    0--3
    |  |
    1--2
    */

    m_pVertexBuffer->SetAt( 0, -nHalfOfImageX ,
	nHalfOfImageY, 0.0f, 0.0f,1.0f); // Left Top  corner
    m_pVertexBuffer->SetAt( 1, -nHalfOfImageX ,
	-nHalfOfImageY, 0.0f, 0.0f,0.0f), // Left Bottom
    m_pVertexBuffer->SetAt( 2, nHalfOfImageX,
	-nHalfOfImageY, 0.0f, 1.0f,0.0f); // Right bottom
    m_pVertexBuffer->SetAt( 3, nHalfOfImageX,
	nHalfOfImageY,  0.0f, 1.0f,1.0f); // Right top
 /// ...................
}
3. BottomWindows

This class is responsible for drawing and message handling of buttons displayed at the bottom of PicZoom.

There are 9 buttons displayed at bottom of PicZoom, which will help to explore different image files in the current folder, zoom, and rotation of image.

BottomWindows.jpg

Screenshot of BottomWindows.

GLButton is designed to handle all operations related to one button. Bottomwindows creates 9 instances of GLButton, and hold in a list, and all commands are passed to the button list.

C++
/*
This class handles all operations related to a Button.
The drawing and mouse message handling is handled in this class.
The resource ID of bitmap is provided to this class, and ID of Message
to send to parentWindow( PicZoomDlg) is also provide to this class.
Whenever user press the button, this class will send message to PicZoomDlg.
*/
class GLButton : public GLWindowBase
{
public:

    GLButton( HWND hParentWindow_i );
    virtual ~GLButton();
    virtual void SetRegion( const int nLeft_i, const int nTop_i,
                    const int nWidth_i, const int nHeight_i );
    virtual void SetImage( const int nResourceID_i );
    virtual bool SetupButton();
    virtual void SetLButtonMessage( const int nMessageToParent_i );
    virtual bool OnMouseMove( const int nX_i, const int nY_i, const int nFlags_i );
    virtual bool OnLButtonDown
		( const int nX_i, const int nY_i, const int nFlags_i );
    virtual bool OnLButtonUp
		( const int nX_i, const int nY_i, const int nFlags_i );
    virtual bool IsWithinRegion( const int nX_i, const int nY_i );
    void SetTransparency( const float fTransparency_i );
    virtual bool Draw();
}; 

The initialization of a GLButton is very simple. Three items are required to initialize a GLButton. The resource ID, region of display and the Message ID. The resource ID of bitmap is used to create button image. The alpha channels of each pixel determine the transparency of button. When alpha values of a pixel is 0.0, then that pixel will not be displayed in button image. When user clicks the button, the message will send to the parent window(PicZoomDlg).

The transparent behaviour of each button is achieved by the alpha blending technique. The bitmaps are created in RGBA format (one 8 bit channel for alpha component). GLTexture class is modified to create RGB8, RGBA8, textures. The bitmaps required for bottom windows are added as resource of PicZoom, and that bitmaps are loaded with BMPLoader, and create GLTexture with RGBA data.

C++
// The texture and vertex buffer for rendering are setup in this function.
bool GLButton::SetupButton()
{
    // Create Vertex buffer.
    m_pVertexBuffer = new GLVertexBuffer;
    if( 0 == m_pVertexBuffer )
    {
        return false;
    }
    // Create Vertex buffer for rendering Quad image.
    m_pVertexBuffer->CreateQuadVertexBuffer();

    UpdateVertexBuffer();

    // Setup Texture from Bitmap resource ID.
    m_pTexture = new GLTexture;
    int nWidth = 0;
    int nHeight = 0;
    BYTE* pbyARGBImage = 0;
    BMPLoader LoadImage;
    // Load Alpha enabled texture.
    LoadImage.LoadBMP( m_nResourceID, nWidth, nHeight, pbyARGBImage, true );
    // Create RGBA format texture.
    m_pTexture->Create( nWidth, nHeight, pbyARGBImage, GL_RGBA, GL_RGBA8 );
    return (GL_NO_ERROR == glGetError());
} 
3. 1. Mouse Over Effect of Button

The mouse over effect of buttons is implemented in a tricky way. When displaying, the blending feature of texel (texture color) with current colour (glColor) is enabled. Then set low color(glColor4f(0.75f, 0.75f, 0.75f, 0.75f)) while displaying button in normal scenario. While displaying button with mouse over high color is applied glColor4f(1.0, 1.0, 1.0, 1.0).

C++
bool GLButton::Draw()
{
    if( m_bMouseOver )
    {
        // After drawing, pixelstore biasing is changed.
        //glColor4f( 1.0, 1.0, 1.0, 1.0 );
        glColor4f( m_fTransparency, m_fTransparency,
			m_fTransparency, m_fTransparency );
    }
    else
    {
        // After drawing, pixelstore biasing is changed.
        //glColor4f( 0.75, 0.75, 0.75, 1.0 );
        glColor4f( 0.75 * m_fTransparency, 0.75 *
	m_fTransparency, 0.75 * m_fTransparency, 0.75 * m_fTransparency );
    }
    m_pVertexBuffer->DrawVertexBuffer( GL_QUADS );
    return true;
} 
3. 2. Displaying functionality of current button

FunctionalityText.jpg

The above screenshot displays the behavior of description text display. GLText is created for displaying a text with smooth show and hide. Here also, alpha blending is used to make smooth appearance and smooth hide.

C++
void GLText::Draw(const int nX_i, const int nY_i)
{
    if( m_StringTimerHide.IsEnabled())
    {
        // Hide old string. Here fColorComponent will decrease
        // after each frame.
        int nRemTime = m_StringTimerHide.GetRemainingTime();
        float fColorComponent = ( nRemTime / 20.0 );
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glColor4f( 1.0, 1.0, 1.0, fColorComponent );
        // Drawing text to screen.
        m_pFontEngine->DrawText( nX_i, nY_i, m_csDisplayString.GetBuffer( 0 ) );
        glDisable( GL_BLEND );
        glColor4f( 1.0, 1.0, 1.0, 1.0 );
        m_StringTimerHide.ElapseTime();
    }
    else
    {
        m_csDisplayString = m_csDisplayStringNew;
        // Show New string. Here fColorComponent will
        // increase during each frame, then reach the maximum value.
        int nRemTime = 20 - m_StringTimerShow.GetRemainingTime();
        float fColorComponent = ( nRemTime / 20.0 );
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glColor4f( 1.0, 1.0, 1.0, fColorComponent );
        // Drawing text to screen.
        m_pFontEngine->DrawText( nX_i, nY_i, m_csDisplayString.GetBuffer( 0 ) );
        glDisable( GL_BLEND );
        glColor4f( 1.0, 1.0, 1.0, 1.0 );
        m_StringTimerShow.ElapseTime();
    }
} 
4. CloseButton

CloseButton is derived from GLButton, in order to implement some additional functionalities in CloseButton. The circular shape of close button is implemented by setting alpha channels to outer region of circle to 0.0 and 1.0 to the inner region of circle. The mouse cursor changing is also based on the circle region.

C++
bool CloseButton::IsWithinRegion( const int nX_i, const int nY_i )
{
    if( GLButton::IsWithinRegion( nX_i, nY_i ))
    {
        // Here check the bottom left corner of circle.
        // If mouse move is not within the semi-circle, then return false.
        int nXDiff = nX_i - GLWindowBase::m_nWidth;
        int nYDiff = nY_i;
        int nRadius = sqrt( (float)nXDiff * nXDiff + (float)nYDiff * nYDiff );
        if( nRadius < 45 )
        {
            return true;
        }
    }
    return false;
} 

One workaround is also included in CloseButton to create smooth edges of semi circle. When semicircle is texture mapped, the edges will not be smooth. Therefore one GLCircle will draw a circle with 50% transparency. Therefore edges of close button will be smooth.

C++
bool CloseButton::Draw()
{
    GLButton::Draw();
    // CloseBoundry draws the outline of circle in 50% transparency.
    m_CloseBoundary.Draw();
    return true;
} 

Slideshow Functionality

Slideshow is also implemented with alpha blending functionality. Two textures are created with bitmap data of two image files. Transition from first image to second is created by blending first and texture texels. When starting slide show, the window size is changed to full desktop size, then hides all other windows (BottomWindows, CloseButton, etc).

C++
void SlideShow::Display()
{
   // glColor3f is used to make small amount of texture display.
    // This color factor is multiplied with texel color and get a shading effect.
    glColor4f( fColorFactor, fColorFactor, fColorFactor, fColorFactor );
    m_pTexture1->Enable();
    // Apply zoom1.
    glScalef( m_fZoom1, m_fZoom1, 0.0 );
    m_pVertexBuffer1->DrawVertexBuffer( GL_QUADS );
    glPopMatrix();

    if( nRemTime < 100 )
    {
        glPushMatrix();
        // When transparent display of second texture is required.
        float fTex2Color = 1.0 - fColorFactor;
        glColor4f( fTex2Color, fTex2Color, fTex2Color, fTex2Color );
        m_pTexture2->Enable();
        // Apply Zoom 2.
        glScalef( m_fZoom2, m_fZoom2, 0.0 );
        m_pVertexBuffer2->DrawVertexBuffer( GL_QUADS );
        glPopMatrix();
    }
}  

Drag and Drop of Image Files

PicZoom supports dragging of image files[*.bmp, *.jpg, *.gif, *.tga]. Drag and Drop is implemented with the help of article from jibesh[http://www.codeproject.com/KB/dialog/JibDragDrop.aspx].

WM_DROPFILES message is handled in CPicZoomDlg class, and file name can be retrieved by DragQueryFile() function. Here is the code to handle new image file loading through drag and drop operation. LoadImage creates a new ImageArea and provides the new file name to this class. ImageArea will display new image file in required zoom and pan.

C++
LRESULT  CPicZoomDlg::OnDropFiles(WPARAM wParam,LPARAM lParam)
{
    TCHAR	szDroppedFile[1024];
    memset( szDroppedFile, 0, sizeof( szDroppedFile ));
    HDROP	hDrop ;
    int nFiles;

    hDrop = (HDROP)wParam;
    nFiles = DragQueryFile(hDrop,		// Structure Identifier
        0,			// -1 to Drop more than one file
        szDroppedFile,// Dropped File Name
        1023 *2 );	// Max char
    // Load new Image file.
    LoadImage( szDroppedFile );
    return 1;
} 

Classes Used in PicZoom

Main Classes
  • PicZoom: Application class of PicZoom.When a new instance of PicZoom starts, it checks already PicZoom running. If one instance is running, then send a message to the old one, and exit.
  • PicZoomDlg: CPicZoomDlg Dialog class. This class initializes OpenGL and, creates main components of PicZoom. All messages received in this class are routed to the GLWindowBase* objects in m_Windows list.
  • BackGround: This class handles drawing of background image displayed in PicZom. When full screen is on, the texture for the desktop image is created in this class. During each draw, this texture is drawn to the screen.
  • BottomWindows: This class holds all windows in the bottom area of PicZoom. All GLButton objects are created in this class, and messages are routed through this class. PicZoomDlg will create BottomWindows, and then send all messages to this class.
  • ImageArea: This class is responsible for drawing of a Image. Whenever a new image file is loaded, ImageArea will handle all operations related to one image. Rotate, Zoom, Translation, are handled in this class. All mouse messages are received from PicZoomDlg to this class, and handle necessary messages. Required timers are started for Zoom, Translate.ImageArea uses UnProject class is used to identify the mouse position is within the image area, and ZoomText class is used to draw current zoom value.
  • CloseButton: This class is responsible for drawing and message handling of close button. Mouse messages are routed to this class. When Mouse is clicked in this region, this class sends WM_QUIT message to PicZoomDlg.
  • SlideShow: This class is responsible for displaying the Slideshow of a folder. This class creates two textures, and makes the combined image with alpha blending. ImageFileFinder class is used to find the image files.
  • PlayButton: This class is derived class of GLButton PlayButton is circular shaped button, therefore mouse message handling is specially handled in this class. IsWithinRegion() is overridden in this class to create a button mouse over effect for circular region of PlayButton. For smooth edge, the outer region of play button is drawn with 2 GLCircle objects.
  • CoordConverter: This class holds current window region, and it converts the window coordinate to opengl coordinate. This can be used for vertex buffer creation with window coordinates.
  • NewFilLoader: This class handles loading of new image. When user clicks context menu, then new (JPG, or BMP) file should be loaded in the old process. When an instance of PicZoom is running, the new instance will set an event and write the name of file in a shared memory, and that memory will be used to get the name of new file name.
  • UnProject: This class is used to get opengl vertex coordinate from screen coordinate, and then identify whether the specified position is within the Image area.
  • ZoomText: This class displays current Zoom factor in center of PicZoom Dialog. Smooth show and hide is implemented with alpha blending.
  • CursorManager: This class creates different cursors, and any other class can use this class for changing the cursor.
  • FileExplorer: This class is responsible for handling the next file and previous file providing to the Dialog class. Whenever user press Next, Previous, Dialog class calls GetNextFileName to retrieve the name of Next/Previous file. On changing the Folder, SetFilePath of this class is called by Dialog. For performance reasons, this class creates a vector and holds all image files in that vector. This vector is created by a new thread.
  • GLWindowBase: Base class of all opengl windows created in PicZoom.
Utility Classes
  • BMPLoader: This class can load a bitmap from a file or Resource. LoadBMP returns the allocated buffer, with width and height. GdiPlus functions are used to load different image file formats.
  • Timer: This class handles Timer functionality. The maximum time is set by SetMaxElapseTime(), and this ElapseTime() reduces time.
  • ScreenCapture: This class takes screen shot of Desktop window. GetBuffer provides RGB data of Desktop window.
  • PicZoomUtil: This class provides static functions for some functionalities required in PicZoom. GetNearestPowerOf2 provides nearest power of 2 of an integer. This function is used for texture creation when non_power_of_two opengl extension is not available.
  • ImageFileFind: A CFileFind derived class which will find image files only.
  • CursorManager: This class creates different cursors, and any other class can use this class for changing the cursor.
OpenGL Wrapper classes:
  • FontEngine: This class handles rendering of all texts in PicZoom. Creating a font display list, and drawing all text. All text drawing is handled with this class. This class holds width and height of all characters to draw string in correct alignment and position.
  • GLButton: This class handles all operations related to a Button.The drawing and mouse message handling is handled in this class. The resource ID of bitmap is provided to this class, and ID of Message to send to parentWindow( PicZoomDlg) is also provide to this class. Whenever user presses the button, this class will send message to PicZoomDlg.
  • GLCirle: This class is used to draw a circle, or semi circle. The start and end angles are provided to this class. The draw action creates a blended circle, and this class is used for creating smooth edges for Play button, and Close button.
  • GLExtension: This class is used to determine GL_ARB_texture_non_power_of_two extension exist or not. m_bNonPowerOfTwo flag is set or reset based on the availability of GL_ARB_texture_non_power_of_two extension.
  • GLSetup: This class setup opengl. Creates opengl rendering context and makes it current.
  • GLText: This class simply handles drawing of Text. Show and hide are very smooth with this class.
  • GLTexture: Handles texture related operations in this class.
  • GLVertexBuffer: This class create a set of vertices of GL_T2F_V3F type. SetAt will update the vertex information of a index. DrawVertexBuffer will pass the vertex information to GPU.

Installer

PicZoomInstaller is created to modify the registry entries, which are required to add context menu in windows explorer. When right click a file, the following context menu will be appeared.

contex2.jpg

The following registry modification is required for creating a context menu in Windows Explorer.

Create a new key “OpenWith PicZoom” under HKEY_CLASSES_ROOT\*\Shell\.

RegistryHandler::RegisterForAllFiles() is responsible for creating a OpenWith PicZoom entry in HKEY_CLASSES_ROOT\*\Shell\.

PicZoomInstaller: Some other registry modification is also implemented, in order to create a new application entry(PicZoom) in open with list.

contex1.jpg

The registry modification is identified by trial and error method. I selected a new program(PicZoom.exe) as default application for opening a BMP file. Then I searched registry and found out the registry location to create an application in open with list. I don’t know any other method to do the same.

The registry entries are created for open with list in bmp, jpg, png, and tga files.

C++
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jpg
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.bmp
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.png
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.tga 

RegistryHandler::AddApplicationName() is responsible for creating PicZoom in open with list of different image files.

Points of Interest

When retrieving the width and height of a character with GetGlyphOutline(), I got a GDI_ERROR. Debug mode works fine, but release mode cause a GDI_ERROR. At last, I found its reason from some forums. The transformation matrix provided to , should be initialised with identity matrix.

Projection Matrix

Since there is no 3D related operation, here we can use orthographic projection. Orthographic projection area is same as the window coordinate, and simply overcame -1 to +1 mapping method of perspective projection.

CPU Usage

Initially I prepared a timer, which will display image in 60 frames per second. This causes high CPU usage of PicZoom without any operation :). That was not good at all. I just prepared different timers for different tasks. For example, when zoom starts, ::SetTimer() is called with TIMER_ZOOM ID. Whenever zoom task is over, ImageArea class will kill this timer, and avoid unwanted draw of image to screen.

Support for NonPowerOfTwo Textures

By default, opengl texture dimension should satisfy power of 2. The width and height of texture should be a power of 2. This can be avoided if your machine supports GL_ARB_texture_non_power_of_two extension.

Initially, GLExtension retrieves the status of GL_ARB_texture_non_power_of_two and updates GLExtension::m_bNonPowerOfTwo. When creating a texture, GLTexture uses this member and decides texture dimension should satisfy non power of two.

C++
bool GLTexture::Create(int nWidth, int nHeight, void *pbyData,
	int nFormat_i, int nInternalFormat_i)
{
    // ...............
    // Retrieve Non power of two support and create texture based on it.
    bool bNonPowerTwo  = (GLExtension::GetInstance()).m_bNonPowerOfTwo;;

    if( bNonPowerTwo )
    {
	    glTexImage2D( GL_TEXTURE_2D, 0, nInternalFormat_i, nWidth, nHeight, 0,
		    nFormat_i, GL_UNSIGNED_BYTE, pbyData );
    }
    else
    {
        // if non-power of two is not supported, need to create nearest n^2 texture.
        int nNewWidth = PicZoomUtil::GetNearestPowerOf2( nWidth );
        int nNewHeight = PicZoomUtil::GetNearestPowerOf2( nHeight );

        int nChannelCount = ( GL_RGB8 == nInternalFormat_i ) ? 3 : 4;
        int nSize = nNewWidth * nNewHeight * nChannelCount;
        if( 0 != ( nNewWidth * nChannelCount ) % 4 )
        {
            nSize = nNewHeight * ceil( ( nNewWidth * nChannelCount ) / 4.0f ) * 4.0f;
        }
        BYTE* pbyDataNew = new BYTE[nSize];
        memset( pbyDataNew, 0, nSize );
        // Set black data
        glTexImage2D( GL_TEXTURE_2D, 0, nInternalFormat_i, nNewWidth, nNewHeight, 0,
                      nFormat_i, GL_UNSIGNED_BYTE, pbyDataNew );
        // Update the required area with input data.
        glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, nWidth, nHeight, nFormat_i,
                         GL_UNSIGNED_BYTE, pbyData );
        delete[] pbyDataNew;
    }
} 

Limitations

Since all images are created as texture, PicZoom requires some graphics memory. In some machines, PicZoom is not starting, the preparation of background image [Desktop background texture] fails. I checked different machines with graphics card, and none of them cause error to start PicZoom. But some machines without graphics card failed to start. When stating PicZoom, an error message similar to the below one may appear, if your machine does not have enough graphics memory to prepare the textures.

TexCreationFailed.jpg

I checked the reason of texture creation failure, in a machine without graphics card. Since some OpenGL applications with multi-texturing are perfectly running in those machines. I changed width and height of these textures to higher values [Modified multi-texturing application to display bitmap of size 1024]. Then I got white rectangle display, instead of proper texture image. I hope this issue is caused by lack of graphic memory to prepare big sized textures.

History

  • 16-Jan-2011: Initial version
  • 24-Jan-2011: Added details of classes used in PicZoom
  • 16-Feb-2011: Modified InstallPicZoom to handle uninstall functionality

License

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