Click here to Skip to main content
15,891,708 members
Articles / Desktop Programming / MFC

PicZoom: A Photo Viewer Created in OpenGL

Rate me:
Please Sign up or sign in to vote.
4.86/5 (63 votes)
16 Feb 2011CPOL16 min read 160.8K   11.6K   150  
PicZoom: A Photo Viewer created in OpenGL
#include "StdAfx.h"
#include "ImageArea.h"
#include "GLTexture.h"
#include "GLVertexBuffer.h"
#include "GLExtension.h"
#include "PicZoomUtil.h"
#include "BMPLoader.h"
#include "FontEngine.h"
#include "ZoomText.h"
#include "UnProject.h"
#include "CursorManager.h"
#include "Consts.h"
#include "GL\gl.h"

#define ZOOM_TIMER_FACTOR 20.0f

ImageArea::ImageArea( HWND hParent, FontEngine* pFontEngine_i ):
GLWindowBase( hParent ),
m_pVertexBuffer( 0 ),
m_pImage( 0 )
{
    SetName( L"ImageArea" );
    m_fXTranslation = 0.0;
    m_fYTranslation = 0.0;
    m_fXTranslationOffSet = 0.0;
    m_fYTranslationOffSet = 0.0;
    m_nWidth = 0;
    m_nHeight = 0;
    m_fZoom = 1.0;
    m_fZoomOffset = 0.0;
    m_nRotate = 0;
    m_bPanEnabled = false;
    m_MouseMoveStart.x = 0;
    m_MouseMoveStart.y = 0;
    m_nResourceID = 0;
    m_pVertexBuffer = new GLVertexBuffer();
    m_pVertexBuffer->Create( 4 );
    m_pZoomText = new ZoomText( pFontEngine_i, m_hParentWindow );
    m_pUnProject = new UnProject( m_fZoom, m_fXTranslation, m_fYTranslation, m_nRotate );
    m_bContinousZoom = false;
}

ImageArea::~ImageArea()
{
    if( m_pUnProject )
    {
        delete m_pUnProject;
        m_pUnProject = 0;
    }
    if( m_pZoomText )
    {
        delete m_pZoomText;
        m_pZoomText = 0;
    }
    if( m_pVertexBuffer )
    {
        delete m_pVertexBuffer;
        m_pVertexBuffer = 0;
    }
}


void ImageArea::SetImageResourceID( const int nResourceID_i )
{
    m_nResourceID = nResourceID_i;
}


void ImageArea::SetImageFilePath( CString csFilePath_i )
{
    m_csFilePath = csFilePath_i;
}


bool ImageArea::SetupWindow()
{
    BMPLoader Loader;
    BYTE* pbyBitmapData = 0;
    if( m_nResourceID ) 
    {
        if( !Loader.LoadBMP( m_nResourceID, m_nImageWidth, m_nImageHeight, pbyBitmapData ))
        {
            return false;
        }
    }
    else
    {
        if( !Loader.LoadBMP( m_csFilePath, m_nImageWidth, m_nImageHeight, pbyBitmapData ))
        {
            return false;
        }
    }
    if( m_pImage )
    {
        delete m_pImage;
        m_pImage = 0;
    }
    m_pImage = new GLTexture;
    if( !m_pImage->Create( m_nImageWidth, m_nImageHeight, pbyBitmapData ))
    {
        return false;
    }
    delete[] pbyBitmapData;

    // Setup Vertex buffer and UnProject.
    int nHalfOfImageY = m_nImageHeight / 2;
    int nHalfOfImageX = m_nImageWidth / 2;
    /*
    0--3
    |  |
    1--2
    */

    float fTextureXMax = 1.0;
    float fTextureYMax = 1.0;
    if( !GLExtension::GetInstance().m_bNonPowerOfTwo )
    {
        fTextureXMax = (float)m_nImageWidth / PicZoomUtil::GetNearestPowerOf2( m_nImageWidth );
        fTextureYMax = (float)m_nImageHeight / PicZoomUtil::GetNearestPowerOf2( m_nImageHeight );
    }

    m_pVertexBuffer->SetAt( 0, -nHalfOfImageX ,  nHalfOfImageY, 0.0f, 0.0f,         fTextureYMax); // 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, fTextureXMax, 0.0f); // Right bottom
    m_pVertexBuffer->SetAt( 3, nHalfOfImageX  ,  nHalfOfImageY, 0.0f, fTextureXMax, fTextureYMax); // Right top

    // Set image area to UnProject instance, this values are used for identifying image position.
    m_pUnProject->SetImageArea( -nHalfOfImageX, -nHalfOfImageY, m_nImageWidth, m_nImageHeight );

    // Zoom handling
    m_ZoomTimer.SetMaxElapseTime( 10 );
    m_fZoomOffset = 1.0 / 10.0;
    m_fZoom = 0.0;

    PrepareZoomDisplay();

    return true;
}

/// Drawing of Image.
bool ImageArea::Draw()
{
    ZoomHandling();
    TranslateHandling();
    RotateHandling();

    m_pImage->Enable();
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    // Y value is -ve, only becouse 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 );
    glScalef( m_fZoom, m_fZoom, 1.0 );
    glRotatef( ROTATION_ANGLES[m_nRotate], 0, 0, 1 );

    m_pVertexBuffer->DrawVertexBuffer( GL_QUADS );

    // After image drawing, drawing Zoom text
    m_pZoomText->Draw();

    return true;
}

void ImageArea::ZoomHandling()
{
    if( m_ZoomTimer.IsEnabled() || m_bContinousZoom )
    {
        if( m_ZoomTimer.IsEnabled())
        {
            m_ZoomTimer.ElapseTime();
            // Zoom on to off
            if( !m_ZoomTimer.IsEnabled())
            {
                /*
                // When Zoom completed, set visibility of bottom windows to true
                if( m_bDisplayBottomWindow )
                {
                    // Display Bottom windows for first time.
                    m_pBottomWindows->SetVisibility( true );
                    m_bDisplayBottomWindow = false;
                }*/
            }
        }
        m_fZoom += ( m_fZoomOffset );
        if( !m_bContinousZoom )
        {
            //m_fZoomOffset -= ( m_fZoomOffset );
        }
        if( m_fZoom < 0.01 )
        {
            m_fZoom = 0.01;
        }
        m_pZoomText->SetZoomValue( m_fZoom );

        int nOffSet =1;
        if( m_fZoomOffset < 0.0 )
        {
            nOffSet = -1;
        }
    int nHalfOfImageY = m_nImageHeight / 2;
    int nHalfOfImageX = m_nImageWidth / 2;
    /*
    0--3
    |  |
    1--2
    */
    /*
    GLVertex VertexData[4];
    // Here No Rotation.
    VertexData[0] = m_pVertexBuffer->Get( 0 );
    VertexData[1] = m_pVertexBuffer->Get( 1 );
    VertexData[2] = m_pVertexBuffer->Get( 2 );
    VertexData[3] = m_pVertexBuffer->Get( 3 );

    m_pVertexBuffer->SetAt( 0, VertexData[0].x - nOffSet, VertexData[0].y + nOffSet, 0.0f, 0.0f,1.0f); // Left Top  corner
    m_pVertexBuffer->SetAt( 1, VertexData[1].x - nOffSet, VertexData[1].y - nOffSet, 0.0f, 0.0f,0.0f), // Left Bottom
    m_pVertexBuffer->SetAt( 2, VertexData[2].x + nOffSet, VertexData[2].y - nOffSet, 0.0f, 1.0f,0.0f); // Right bottom
    m_pVertexBuffer->SetAt( 3, VertexData[3].x + nOffSet, VertexData[3].y + nOffSet,  0.0f, 1.0f,1.0f); // Right top
*/
    }
    else
    {
        m_fZoomOffset = 0.0;
        ::KillTimer( m_hParentWindow, TIMER_ZOOM );
    }
}

void ImageArea::TranslateHandling()
{
    if( m_TranslateTimer.IsEnabled())
    {
        m_TranslateTimer.ElapseTime();
        if( 0 != m_fXTranslationOffSet )
        {
            m_fXTranslation += m_fXTranslationOffSet / 2.0;
            m_fXTranslationOffSet += -1 * ( m_fXTranslationOffSet / 2.0);
        }
        if( 0 != m_fYTranslationOffSet )
        {
            m_fYTranslation += m_fYTranslationOffSet / 2.0;
            m_fYTranslationOffSet += -1 * (m_fYTranslationOffSet / 2.0);
        }
    }
    else
    {
        m_fYTranslationOffSet = 0;
        m_fXTranslationOffSet = 0;
        if( !m_bPanEnabled )
        {
            ::KillTimer( m_hParentWindow, TIMER_TRANSLATE );
        }
    }
}

void ImageArea::RotateHandling()
{
    if( m_RotateTimer.IsEnabled() )
    {
        m_RotateTimer.ElapseTime();
        --m_nRotate;
        if( 0 == m_nRotate )
        {
            ::KillTimer( m_hParentWindow, TIMER_MAKE_TO_CENTER );
        }
        // Make rotate index to 0.
        max( 0, m_nRotate );
    }
}

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

    // Subin's comment, restrict Zoom to a maximum value.
    // Then its okay, I blocked zoom event after 3000.
    if( m_fZoom > ZOOM_MAX_VALUE && fZoomValue > 0.0 )
    {
        return true;
    }

    if( 0 == m_fZoomOffset )
    {
        ::SetTimer( m_hParentWindow, TIMER_ZOOM, 5, 0 );
    }
    
    // Findout 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;
}

void ImageArea::PrepareZoomDisplay()
{
    int nTimerCount = m_bDesktopCaptured ? 10 : 1;
    m_fZoom = 0.0;
    // Make Image Fit into the Window region.
    if( m_nImageWidth > ( m_nWidth - 40 ) || m_nImageHeight > ( m_nHeight + 40 ) )
    {
        // Find out Zoom factor.
        float fXDirZoom = ( m_nWidth - 40 ) / float( m_nImageWidth );
        float fYDirZoom = ( m_nHeight - 40 ) / float( m_nImageHeight );
        m_fZoomOffset = ( min( fXDirZoom, fYDirZoom )) / float( nTimerCount );
        m_ZoomTimer.SetMaxElapseTime( nTimerCount );
        ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
    }
    else
    {
        m_fZoomOffset = 1.0 / nTimerCount;
        m_ZoomTimer.SetMaxElapseTime( nTimerCount );
        ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
    }
}

bool ImageArea::ZoomPlus( const bool bOn_i )
{
    if( bOn_i )
    {
        // Action start
        if( 0 == m_fZoomOffset )
        {
            ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
        }
        m_fZoomOffset += 1.0 / 100.0;
//        m_ZoomTimer.SetMaxElapseTime( 1 );
        m_bContinousZoom = true;
    }
    else
    {
        // Off zoom timer. After infinity off, one frame will be displayed.
        //m_ZoomTimer.EnableInfinity( false );
        m_bContinousZoom = false;
    }
    return true;
}
bool ImageArea::ZoomMin( const bool bOn_i )
{
    if( bOn_i )
    {
        // Action start
        if( 0 == m_fZoomOffset )
        {
            ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
        }
        m_fZoomOffset -= ( ( 2.5 / 100.0 ) ) / 2;
        //m_ZoomTimer.SetMaxElapseTime( 1 );
        //m_ZoomTimer.EnableInfinity( true );
        m_bContinousZoom = true;
    }
    else
    {
        // Off zoom timer. After infinity off, one frame will be displayed.
       // m_ZoomTimer.EnableInfinity( false );
        m_bContinousZoom = false;
    }
    return true;
}
bool ImageArea::ZoomOneToOne()
{
    ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
    m_fZoomOffset = ( 1.0 - m_fZoom ) / 10.0;
    m_ZoomTimer.SetMaxElapseTime( 10 );
    return true;
}
bool ImageArea::MakeToCenter()
{
    // Here find the Zoom to make the image Center.
    m_fXTranslationOffSet = -m_fXTranslation;
    m_fYTranslationOffSet = -m_fYTranslation;
    m_TranslateTimer.SetMaxElapseTime( 10 );
    ::SetTimer( m_hParentWindow, TIMER_TRANSLATE, 10, 0 );

    // When Zoom is applied, avoid the unwanted zoom.
    // Find zoom factor for image display.
    float fXDirZoom = m_nWidth / (float)m_nImageWidth;
    float fYDirZoom = m_nHeight / (float)m_nImageHeight;
    // Get minimum zoom, that can be used for displaying full screen Drawing.
    float fExpectedZoom = min( fXDirZoom, fYDirZoom );

    // When High zoom is possible, then avoid the zoom.
    fExpectedZoom = min( 1.0, fExpectedZoom );
    m_fZoomOffset = -( m_fZoom - fExpectedZoom );
    if( m_fZoomOffset < 0.0 || m_fZoomOffset > 0.0 )
    {
        m_fZoomOffset = m_fZoomOffset / 10.0;
        m_ZoomTimer.SetMaxElapseTime( 10 );
        ::SetTimer( m_hParentWindow, TIMER_ZOOM, 10, 0 );
    }

    // When image is in rotated state, then make to original state.
    // This rotation is set only if translation is applied, 
    // else make to non-rotated image.
    if( m_nRotate && ( m_fYTranslationOffSet > 0.0 || m_fXTranslationOffSet > 0.0 ))
    {
        // Here start rotation,
        m_RotateTimer.SetMaxElapseTime( m_nRotate );
        // Enable timer to start rotation.
        ::SetTimer( m_hParentWindow, TIMER_MAKE_TO_CENTER, 10, 0 );
    }
    else
    {
        m_nRotate = 0;
    }

    return true;
}

bool ImageArea::RotateAntiClockWise()
{
    m_nRotate--;
    if( m_nRotate < 0 )
    {
        m_nRotate = 3;
    }
    return true;
}

bool ImageArea::RotateClockWise()
{
    m_nRotate++;
    if( m_nRotate >= 4 )
    {
        m_nRotate = 0;
    }
    return true;
}

void ImageArea::GetImageArea( int& nImageWidth_o, int& nImageHeight_o )
{
    nImageWidth_o = m_nImageWidth * m_fZoom;
    nImageHeight_o = m_nImageHeight * m_fZoom;
}

void ImageArea::GetTranslation( int& nXTranslation_o, int& nYTranslation_o )
{
    nXTranslation_o = m_fXTranslation;
    nYTranslation_o = m_fYTranslation;
}
void ImageArea::SetTranslation( const int nXTranslation_i, const int nYTranslation_i )
{
    m_fXTranslation = nXTranslation_i;
    m_fYTranslation = nYTranslation_i;
}
bool ImageArea::IsWithinImageArea( const int nX_i, const int nY_i )
{
    return m_pUnProject->IsWithinRange( nX_i, nY_i );
}

bool ImageArea::OnLButtonDown( const int nX_i, const int nY_i, const int nFlags_i )
{
    if( m_pUnProject->IsWithinRange( nX_i, nY_i ))
    {
        if( !m_bPanEnabled )
        {
            ::SetTimer( m_hParentWindow, TIMER_TRANSLATE, 16, 0 );
            m_MouseMoveStart.x = nX_i;
            m_MouseMoveStart.y = nY_i;

            // ::ScreenToClient( m_MouseMoveStart );
            m_bPanEnabled = true;
        }
        HCURSOR hHandCursor = CursorManager::GetInstance()->GetCursor( CURSOR_HAND_TYPE );
        CursorManager::GetInstance()->SetActiveCursor( CURSOR_HAND_TYPE );
        ::SetCursor( hHandCursor );
        return true;
    }
    return false;
}

bool ImageArea::OnLButtonUp( const int nX_i, const int nY_i, const int nFlags_i )
{
    if( CursorManager::GetInstance()->GetCurrentCursor() == CursorManager::GetInstance()->GetCursor( CURSOR_HAND_TYPE ) )
    {
        HCURSOR hMoveCursor = CursorManager::GetInstance()->GetCursor( CURSOR_MOVE_TYPE );
        CursorManager::GetInstance()->SetActiveCursor( CURSOR_MOVE_TYPE );
        ::SetCursor( hMoveCursor );
    }
    m_bPanEnabled = false;

    // Nothing handled in Button up. Should transfer LButton up to other windows.
    return false;
}

bool ImageArea::OnMouseMove( const int nX_i, const int nY_i, const int nFlags_i )
{
    bool bReturnValue = false;
    if( MK_LBUTTON == ( nFlags_i & MK_LBUTTON ))
    {
        if( m_bPanEnabled )
        {
            POINT CursorPos;
            GetCursorPos( &CursorPos );
            ::ScreenToClient( m_hParentWindow, &CursorPos );
            POINT point = CursorPos;
            m_fXTranslationOffSet += ( point.x - m_MouseMoveStart.x );
            m_fYTranslationOffSet += ( point.y - m_MouseMoveStart.y );
            m_TranslateTimer.SetMaxElapseTime( 4, true );
            m_MouseMoveStart = point;
            (CursorManager::GetInstance())->SetActiveCursor( CURSOR_HAND_TYPE );
            bReturnValue = true;
        }
    }
    else
    {
        if( m_pUnProject->IsWithinRange( nX_i, nY_i ))
        {
            (CursorManager::GetInstance())->SetActiveCursor( CURSOR_MOVE_TYPE );
            HCURSOR hCur = (CursorManager::GetInstance())->GetCurrentCursor();
            SetCursor( hCur );
            bReturnValue = true;
        }
        else
        {
            (CursorManager::GetInstance())->SetActiveCursor( CURSOR_NORMAL_TYPE );
            HCURSOR hCur = (CursorManager::GetInstance())->GetCurrentCursor();
            SetCursor( hCur );
            bReturnValue = false;
        }
    }
    return bReturnValue;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions