PicZoom: A Photo Viewer Created in OpenGL






4.86/5 (64 votes)
PicZoom: A Photo Viewer created in OpenGL
- Download PicZoom source - 171.92 KB
- Download InstallPicZoom source - 17.92 KB
- Download PicZoom binaries - 455.41 KB
Table of Contents
- Introduction
- Background
- Using the code
- How to Capture Screenshot of Desktop
- Main Components
- Functionalities and Implementation of 4 components
- Slideshow functionality
- Drag and Drop of Image files
- Classes Used in PicZoom
- Main Classes
- Utility Classes
- OpenGL Wrapper classes
- Installer
- Points of Interest
- Limitations
- History
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.
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:
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.
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 );
}
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
, all mouse messages are also passed to the components. Draw
()
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.
// 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 e
ntire screen. When switching from Desktop mode to Dialog mode, the Background
instance is deleted from the WindowList
and the background drawing is avoided.
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.
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
.
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;
}
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
.
// 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:
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.
/// 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.

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.
// 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.
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.
/*
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.
// 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).
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
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.
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.
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.
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).
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.
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 ofPicZoom
.When a new instance ofPicZoom
starts, it checks alreadyPicZoom
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 ofPicZoom
. All messages received in this class are routed to theGLWindowBase*
objects inm_Windows
list.BackGround
: This class handles drawing of background image displayed inPicZom
. 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 ofPicZoom
. AllGLButton
objects are created in this class, and messages are routed through this class.PicZoomDlg
will createBottomWindows
, and then send all messages to this class.ImageArea
: This class is responsible for drawing of aImage
. 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 fromPicZoomDlg
to this class, and handle necessary messages. Required timers are started forZoom
,Translate.ImageArea
usesUnProject
class is used to identify the mouse position is within the image area, andZoomText
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 sendsWM_QUIT
message toPicZoomDlg
.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 ofGLButton 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 ofPlayButton
. For smooth edge, the outer region of play button is drawn with 2GLCircle
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 ofPicZoom
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 theImage
area.ZoomText
: This class displays currentZoom
factor in center ofPicZoom
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 theDialog
class. Whenever user pressNext
,Previous
,Dialog
class callsGetNextFileName
to retrieve the name of Next/Previous file. On changing the Folder,SetFilePath
of this class is called byDialog
. 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 inPicZoom
.
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 handlesTimer
functionality. The maximum time is set bySetMaxElapseTime()
, and thisElapseTime()
reduces time.ScreenCapture
: This class takes screen shot of Desktop window.GetBuffer
provides RGB data of Desktop window.PicZoomUtil
: This class providesstatic
functions for some functionalities required inPicZoom
.GetNearestPowerOf2
provides nearest power of 2 of an integer. This function is used for texture creation whennon_power_of_two opengl
extension is not available.ImageFileFind
: ACFileFind
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 inPicZoom
. 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 drawstring
in correct alignment and position.GLButton
: This class handles all operations related to aButton
.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 toparentWindow( PicZoomDlg)
is also provide to this class. Whenever user presses the button, this class will send message toPicZoomDlg
.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 determineGL_ARB_texture_non_power_of_two
extension exist or not.m_bNonPowerOfTwo
flag is set or reset based on the availability ofGL_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 ofGL_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.
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.
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.
// 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.
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.
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