/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: pxgifrnd.cpp,v 1.10.6.2 2004/07/09 12:55:11 pankajgupta Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

// include
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxcom.h"
#include "hxassert.h"

#include <stdio.h>
#ifdef _WINDOWS
#include <windows.h>
#include "platform/win/resource.h"
#endif
#if defined(_UNIX) && defined(USE_XWINDOWS)
#include <X11/cursorfont.h>
#include <X11/Intrinsic.h>
#endif
#ifdef _MACINTOSH
#include <ctype.h>
#include "platform/mac/cresload.h"
extern FSSpec g_DLLFSpec;
//CResourceLoader* CResourceLoader::zm_theResourceLoader = NULL;
#endif

//Mini sites don't support ARGB blt'ing.
//Should break this out into a ALPHA_SUPPORTED feature.
#if !defined(HELIX_FEATURE_MINI_SITE)
#  define ARGB_CID HX_ARGB
#else
#  define ARGB_CID HX_RGB
#endif

#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxcore.h"
#include "hxrendr.h"
#include "hxhyper.h"
#include "hxplugn.h"
#include "hxwin.h"
#include "hxasm.h"
#include "hxevent.h"
#include "hxvsurf.h"
#include "hxver.h"
#include "hxupgrd.h"
#include "hxengin.h"
#include "hxmon.h"
#include "hxerror.h"
#include "hxclsnk.h"
#include "hxprefs.h"
#include "addupcol.h"
#include "hxstrutl.h"
#include "netbyte.h"
#include "unkimp.h"
#include "baseobj.h"
#include "hxparse.h"
#include "hxtick.h"
#include "hxbuffer.h"
#include "hxstring.h"
#include "gstring.h"
#include "glist.h"
#include "pxutil.h"
#include "pxrect.h"
#include "pxcolor.h"
#include "pxeffect.h"
#include "parseurl.h"
#include "pxcallbk.h"
#include "pximage.h"
#include "makebomb.h"
#include "pxtransp.h"
#include "cladvsnk.h"
#include "baseobj.h"
#include "unkimp.h"
#include "lzw.h"
#include "gifimage.h"
#include "gifcodec.h"
#include "gifrmlog.h"
#include "pxgifrnd.h"
#include "gifrdll.ver"

#include "debugout.h"
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE     
static const char HX_THIS_FILE[] = __FILE__;
#endif
#include "errdbg.h"

#ifdef _AIX
#include "dllpath.h"
ENABLE_MULTILOAD_DLLACCESS_PATHS(Pxgifrnd);
#endif

const char * const CGIFRenderer::m_pszName                          = "GIF";
const char * const CGIFRenderer::m_pszDescription                   = "Helix GIF Renderer Plugin";
const char * const CGIFRenderer::m_pszCopyright                     = HXVER_COPYRIGHT;
const char * const CGIFRenderer::m_pszMoreInfoURL                   = HXVER_MOREINFO;
const char * const CGIFRenderer::m_ppszStreamMimeType[]             = {"application/vnd.rn-gifstream", 
                                                                 "application/vnd.rn-gifstream2",
                                                                 "application/vnd.rn-gifstream3",
                                                                 NULL};
const UINT32 CGIFRenderer::m_ulHighestSupportedContentVersion = HX_ENCODE_PROD_VERSION(0, 0, 0, 0);
const UINT32 CGIFRenderer::m_ulHighestSupportedStreamVersion  = HX_ENCODE_PROD_VERSION(0, 1, 0, 0);

#ifdef _WINDOWS
extern HINSTANCE g_hInstance;
#endif

CGIFRenderer::CGIFRenderer()
{
    MLOG_LEAK("CON CGIFRenderer this=0x%08x\n", this);
    m_lRefCount            = 0;
    m_pContext             = NULL;
    m_pStream              = NULL;
    m_cSize.cx             = 0;
    m_cSize.cy             = 0;
    m_pMISUS               = NULL;
    m_pMISUSSite           = NULL;
    m_pCommonClassFactory  = NULL;
    m_pHyperNavigate       = NULL;
    m_pStatusMessage       = NULL;
    m_pGIFCodec            = NULL;
    m_ulPadWidth           = 0;
    m_bDecompressFinished  = FALSE;
    m_ulCurImg             = 0;
    m_ulCurImgRenderTime   = 0;
    m_pOutputBuffer        = NULL;
    m_ucTarget             = kTargetBrowser;
    m_cURL                 = "";
    m_ulBackgroundColor    = 0x00FFFFFF;
    m_pScheduler           = NULL;
    m_ulDataWidth          = 0;
    m_ulLoopsDone          = 0;
    m_ulBitsPerPixel       = 32;
    m_ulBytesPerPixel      = 4;
    m_bRGBOrdering         = TestBigEndian();
    m_bFirstTimeSync       = TRUE;
    m_bFirstDraw           = TRUE;
    m_lLastImg             = -1;
    m_bSiteAttached        = FALSE;
    m_ulCurDelayTime       = 0;
    m_pErrorMessages       = NULL;
#if defined(_WINDOWS)
    m_bRowsInverted        = TRUE;
#elif defined(_MACINTOSH)
    m_bRowsInverted        = FALSE;
#elif defined(_UNIX)
    m_bRowsInverted        = FALSE;
#endif
    m_bStatusMsgWillNeedErasing = FALSE;
    m_bSetHyperlinkCursor    = FALSE;
    m_sOldMouseX             = -1;
    m_sOldMouseY             = -1;
#if defined(_WINDOWS)
    m_hPreHyperlinkCursor    = NULL;
    m_hHyperlinkCursor       = NULL;
#elif defined(_MACINTOSH)
    const short HAND_CURSOR  = 1313;
    m_pResourceLoader = CResourceLoader::CreateInstance(g_DLLFSpec);
    m_hHyperlinkCursor = (CursHandle)m_pResourceLoader->LoadResource('CURS', HAND_CURSOR);
    m_eCurrentCursor         = CURSOR_ARROW;
#elif defined(_UNIX) && defined(USE_XWINDOWS)
    m_pDisplay             = 0;
    m_Window               = 0;
    m_hHyperlinkCursor     = -1;
    m_hCurrentCursor       = -1;
#endif
    m_pStreamHeaderBuffer  = NULL;
    m_ulStreamHeaderOffset = 0;
    m_bIgnorePackets       = FALSE;
    m_pCallback            = NULL;
    m_bImageBombed         = FALSE;
    m_ulRendererFlags      = 0;
    m_ulWidth              = 0;
    m_ulHeight             = 0;
    m_pValues              = NULL;
    m_ulBackgroundOpacity         = 255;
    m_ulMediaOpacity              = 255;
    m_ulMediaChromaKey            = 0;
    m_bMediaChromaKeySpecified    = FALSE;
    m_ulMediaChromaKeyTolerance   = 0;
    m_ulMediaChromaKeyOpacity     = 0;
    m_bUsesAlphaChannel           = FALSE;
    m_bPreserveMediaRepeat        = TRUE;
    m_bPaused                     = FALSE;
    m_lTimeOffset                 = 0;
    m_tSchedulerTimeBase.tv_sec   = 0;
    m_tSchedulerTimeBase.tv_usec  = 0;
    m_ulTimeAtSchedulerTimeBase   = 0;
    m_ulCurFrameIndex             = 0xFFFFFFFF;
    m_pClientAdviseSink           = NULL;
    m_ulEndTime                   = 0;
    m_bNoNativeSize               = FALSE;
    m_bCanBltSubRects             = FALSE;
}

CGIFRenderer::~CGIFRenderer()
{
    MLOG_LEAK("DES CGIFRenderer this=0x%08x\n", this);
    if (m_pCallback)
    {
        if (m_pCallback->IsCallbackPending())
        {
            m_pCallback->RemovePendingCallback();
        }
    }
    if (m_pClientAdviseSink)
    {
        m_pClientAdviseSink->Close();
    }
    HX_RELEASE(m_pStatusMessage);
    HX_RELEASE(m_pOutputBuffer);
    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pCommonClassFactory);
    HX_RELEASE(m_pHyperNavigate);
    HX_DELETE(m_pGIFCodec);
    HX_RELEASE(m_pScheduler);
    HX_RELEASE(m_pErrorMessages);
    
#if defined(_MACINTOSH)
    if (m_hHyperlinkCursor)
    {
        m_pResourceLoader->UnloadResource((Handle)m_hHyperlinkCursor);
        m_hHyperlinkCursor = NULL;
        
        HX_RELEASE(m_pResourceLoader);
    }
#endif
#if defined(_UNIX) && defined(USE_XWINDOWS)
    if ((m_hHyperlinkCursor != -1) && m_pDisplay)
    {
    XLockDisplay(m_pDisplay);
    XFreeCursor(m_pDisplay, m_hHyperlinkCursor);
    XUnlockDisplay(m_pDisplay);
    m_hHyperlinkCursor = -1;
    }
#endif
    HX_RELEASE(m_pStreamHeaderBuffer);
    HX_RELEASE(m_pCallback);
    HX_RELEASE(m_pValues);
    HX_RELEASE(m_pClientAdviseSink);
}


STDMETHODIMP CGIFRenderer::QueryInterface(REFIID riid, void** ppvObj)
{
    HX_RESULT retVal = HXR_OK;

    if (ppvObj)
    {
        // Set default
        *ppvObj = NULL;
        // Check for IID type
        if (IsEqualIID(riid, IID_IUnknown))
        {
            AddRef();
            *ppvObj = (IUnknown*) (IHXPlugin*) this;
        }
        else if (IsEqualIID(riid, IID_IHXPlugin))
        {
            AddRef();
            *ppvObj = (IHXPlugin*) this;
        }
        else if (IsEqualIID(riid, IID_IHXRenderer))
        {
            AddRef();
            *ppvObj = (IHXRenderer*) this;
        }
        else if (IsEqualIID(riid, IID_IHXSiteUser))
        {
            AddRef();
            *ppvObj = (IHXSiteUser*) this;
        }
        else if (IsEqualIID(riid, IID_IHXSiteUserSupplier))
        {
            if (m_pMISUS)
            {
                return m_pMISUS->QueryInterface(IID_IHXSiteUserSupplier, ppvObj);
            }
            else
            {
                retVal = HXR_UNEXPECTED;
            }
        }
        else if (IsEqualIID(riid, IID_IHXStatistics))
        {
            AddRef();
            *ppvObj = (IHXStatistics*) this;
        }
        else if (IsEqualIID(riid, IID_IHXValues))
        {
            AddRef();
            *ppvObj = (IHXValues*) this;
        }
        else if (IsEqualIID(riid, IID_IHXUpdateProperties))
        {
            AddRef();
            *ppvObj = (IHXUpdateProperties*) this;
        }
        else
        {
            retVal = HXR_NOINTERFACE;
        }
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

STDMETHODIMP_(UINT32) CGIFRenderer::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}


STDMETHODIMP_(UINT32) CGIFRenderer::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;

    return 0;
}


STDMETHODIMP CGIFRenderer::GetPluginInfo(REF(BOOL)         rbLoadMultiple,
                                         REF(const char *) rpszDescription,
                                         REF(const char *) rpszCopyright,
                                         REF(const char *) rpszMoreInfoURL,
                                         REF(UINT32)       rulVersionNumber)
{
    rbLoadMultiple   = TRUE;
    rpszDescription  = (const char*) m_pszDescription;
    rpszCopyright    = (const char*) m_pszCopyright;
    rpszMoreInfoURL  = (const char*) m_pszMoreInfoURL;
    rulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::InitPlugin(IUnknown* pContext)
{
    HX_RESULT retVal = HXR_FAIL;

    if (pContext)
    {
        // Save a copy of the calling context
        HX_RELEASE(m_pContext);
        m_pContext = pContext;
        m_pContext->AddRef();
        // Get a IHXCommonClassFactory interface
        HX_RELEASE(m_pCommonClassFactory);
        retVal = m_pContext->QueryInterface(IID_IHXCommonClassFactory,
                                            (void**) &m_pCommonClassFactory);
        if (SUCCEEDED(retVal))
        {
#if defined(HELIX_FEATURE_HYPER_NAVIGATE)
            // Get an IHXHyperNavigate interface
            HX_RELEASE(m_pHyperNavigate);
            m_pContext->QueryInterface(IID_IHXHyperNavigate,
                                       (void**) &m_pHyperNavigate);
#endif /* #if defined(HELIX_FEATURE_HYPER_NAVIGATE) */
            // Get the IHXStatusMessage interface - it's OK if TLC doesn't support
            // this, so we won't check error return.
            HX_RELEASE(m_pStatusMessage);
            m_pContext->QueryInterface(IID_IHXStatusMessage,
                                       (void**) &m_pStatusMessage);
            // Get an IHXScheduler interface
            HX_RELEASE(m_pScheduler);
            retVal = m_pContext->QueryInterface(IID_IHXScheduler,
                                                (void**) &m_pScheduler);
            if (SUCCEEDED(retVal))
            {
                // Get the IHXErrorMessages interface - OK if
                // TLC doesn't support it
                HX_RELEASE(m_pErrorMessages);
                m_pContext->QueryInterface(IID_IHXErrorMessages,
                                           (void**) &m_pErrorMessages);
                // Create a PXCallback object
                HX_RELEASE(m_pCallback);
                m_pCallback = new PXCallback();
                if (m_pCallback)
                {
                    // AddRef the object
                    m_pCallback->AddRef();
                    // Init the callback object
                    retVal = m_pCallback->Init(m_pContext, this);
                    if (SUCCEEDED(retVal))
                    {
                        // Create the IHXValues object
                        HX_RELEASE(m_pValues);
                        m_pCommonClassFactory->CreateInstance(CLSID_IHXValues,
                                                              (void**) &m_pValues);
                    }
                }
                else
                {
                    retVal = HXR_OUTOFMEMORY;
                }
            }
        }
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetRendererInfo(REF(const char **) rppszStreamMimeType,
                                           REF(UINT32)        rulInitialGranularity)
{
    rppszStreamMimeType   = (const char**) m_ppszStreamMimeType;
    rulInitialGranularity = 1000;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::StartStream(IHXStream* pStream, IHXPlayer* pPlayer)
{
    HX_RESULT retVal = HXR_OK;

    if (pStream && pPlayer)
    {
        // Save a copy of the stream
        HX_RELEASE(m_pStream);
        m_pStream = pStream;
        m_pStream->AddRef();
        // Create a PXClientAdviseSink object
        HX_RELEASE(m_pClientAdviseSink);
        m_pClientAdviseSink = new PXClientAdviseSink();
        if (m_pClientAdviseSink)
        {
            // AddRef the object
            m_pClientAdviseSink->AddRef();
            // Init the object - this registers this renderer
            // as a client advise sink
            retVal = m_pClientAdviseSink->Init(pPlayer,
                                               (PXClientAdviseSinkResponse*) this);
        }
        else
        {
            retVal = HXR_OUTOFMEMORY;
        }
#if defined(HELIX_FEATURE_MISU)
        if (SUCCEEDED(retVal))
        {
            // Create a IHXMultiInstanceSiteUserSupplier interface
            HX_RELEASE(m_pMISUS);
            retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXMultiInstanceSiteUserSupplier,
                                                           (void**) &m_pMISUS);
            if (SUCCEEDED(retVal))
            {
                // Register ourselves as a site user
                retVal = m_pMISUS->SetSingleSiteUser((IUnknown*) (IHXSiteUser*) this);
            }
        }
#endif
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::OnHeader(IHXValues *pStreamHeaderObj)
{
    // Check for input error
    if (pStreamHeaderObj == NULL)
    {
        return HXR_FAIL;
    }

    // Get content version from header
    UINT32    ulIncomingContentVersion;
    HX_RESULT retVal = pStreamHeaderObj->GetPropertyULONG32("ContentVersion", ulIncomingContentVersion);
    if (retVal != HXR_OK)
    {
        return HXR_FAIL;
    }

    // Get stream version from header
    UINT32 ulIncomingStreamVersion;
    retVal = pStreamHeaderObj->GetPropertyULONG32("StreamVersion", ulIncomingStreamVersion);
    if (retVal != HXR_OK)
    {
        return HXR_FAIL;
    }

    // Check the versions
    if (ulIncomingContentVersion > m_ulHighestSupportedContentVersion ||
        ulIncomingStreamVersion  > m_ulHighestSupportedStreamVersion)
    {
        // Add to AU collection
        AddToAutoUpgradeCollection(m_ppszStreamMimeType[2], m_pContext);
        // Now we return an error, any error actually
        return HXR_NO_RENDERER;
    }

    // Get the opaque data from the stream header
    IHXBuffer *pBuffer = NULL;
    retVal = pStreamHeaderObj->GetPropertyBuffer("OpaqueData", pBuffer);
    if (retVal != HXR_OK)
    {
        return retVal;
    }

    // Unpack some info
    BYTE *pData = pBuffer->GetBuffer();
    UnPack8(pData,      m_ucTarget);
    // These are no longer used by the renderer they are parsed in the new hypernavigate object in the core so all renderer
    // commands are consistent and parsed in one place.  We do have to skip over them since the file format still puts them in.
//  UnPack8(pData,      m_ucURLType);
//  UnPack32(pData,     m_ulSeekTime);
    pData += 5;     // Even though the 2 above members were removed we still need to increment the data pointer
    // Note that we don't get the alpha from the background color
    // in the opaque part of the stream header - we get that from
    // the "bgOpacity" CString property in the stream header
    BYTE ucBgRed   = pData[0];
    BYTE ucBgGreen = pData[1];
    BYTE ucBgBlue  = pData[2];
    m_ulBackgroundColor = (ucBgRed << 16) | (ucBgGreen << 8) | ucBgBlue;
    pData += 4;
    UnPackString(pData, m_cURL);

    // Get the current URL
    IHXStreamSource* pStreamSource = NULL;
    retVal = m_pStream->GetSource(pStreamSource);
    if (retVal != HXR_OK || !pStreamSource)
    {
        HX_RELEASE(pBuffer);
        return HXR_FAIL;
    }
    const char* pszURL = pStreamSource->GetURL();
    if (!pszURL)
    {
        HX_RELEASE(pBuffer);
        HX_RELEASE(pStreamSource);
        return HXR_FAIL;
    }
    CHXString cOriginalURL = pszURL;
    HX_RELEASE(pStreamSource);

    // Check the URL to see if it's relative
    if (m_cURL.length() > 0)
    {
        if (m_ucTarget == kTargetPlayer)
        {
            if (IsURLRelative(m_cURL.c_str()) &&
                !strstr(m_cURL.c_str(), "command:"))
            {
                CHXString cRelURL(m_cURL.c_str());
                CHXString cAbsURL;
                retVal = MakeAbsoluteURL(cOriginalURL, cRelURL, cAbsURL);
                if (retVal == HXR_OK)
                {
                    m_cURL = (const char *) cAbsURL;
                }
            }
        }
    }

    // Get the renderer flags property
    m_ulRendererFlags = 0;
    pStreamHeaderObj->GetPropertyULONG32("RendererFlags", m_ulRendererFlags);

    // Check bit 0 to see if parsing failed
    m_ulWidth          = 0;
    m_ulHeight         = 0;
    UINT32 ulBytesUsed = 0;
    if (m_ulRendererFlags & GIF_RENDERER_FLAG_PARSEFAILED)
    {
        // Get the width and height from the stream header
        pStreamHeaderObj->GetPropertyULONG32("Width",  m_ulWidth);
        pStreamHeaderObj->GetPropertyULONG32("Height", m_ulHeight);
        if (!m_ulWidth || !m_ulHeight)
        {
            m_ulWidth       = 48;
            m_ulHeight      = 48;
            m_bNoNativeSize = TRUE;
        }
    }
    else
    {
        // Create a CGIFCodec
        HX_DELETE(m_pGIFCodec);
        m_pGIFCodec = new CGIFCodec();
        if (!m_pGIFCodec)
        {
            HX_RELEASE(pBuffer);
            return HXR_OUTOFMEMORY;
        }

        // Initialize the CGIFCodec for decompression
        ulBytesUsed = pData - pBuffer->GetBuffer();
        retVal = m_pGIFCodec->InitDecompress(pData, pBuffer->GetSize() - ulBytesUsed);
        if (retVal != HXR_OK)
        {
            HX_RELEASE(pBuffer);
            HX_DELETE(m_pGIFCodec);
            return retVal;
        }

        // Set the width and height
        m_ulWidth  = m_pGIFCodec->GetLogicalScreenWidth();
        m_ulHeight = m_pGIFCodec->GetLogicalScreenHeight();
    }

    // Get the mediaRepeat string
    IHXBuffer* pMediaRepeatStr = NULL;
    pStreamHeaderObj->GetPropertyCString("mediaRepeat",
                                         pMediaRepeatStr);
    if (pMediaRepeatStr)
    {
        if (!strcmp((const char*) pMediaRepeatStr->GetBuffer(), "strip"))
        {
            m_bPreserveMediaRepeat = FALSE;
        }
    }
    HX_RELEASE(pMediaRepeatStr);

    // Save the stream header's opaque buffer
    HX_RELEASE(m_pStreamHeaderBuffer);
    m_pStreamHeaderBuffer = pBuffer;
    m_pStreamHeaderBuffer->AddRef();
    // Init the stream header buffer offset
    m_ulStreamHeaderOffset = ulBytesUsed;

    // Now we can release the IHXBuffer
    HX_RELEASE(pBuffer);

     // Create an output buffer
    HX_RELEASE(m_pOutputBuffer);
    retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void **) &m_pOutputBuffer);
    if (retVal != HXR_OK)
    {
        HX_DELETE(m_pGIFCodec);
        return retVal;
    }
    
    // Compute the size of the output buffer
    m_ulDataWidth      = m_ulWidth * m_ulBytesPerPixel;
    m_ulPadWidth       = (m_ulDataWidth + 3) & ~3; // This rounds up to nearest multiple of 4
    UINT32 ulNumBytes  = m_ulPadWidth * m_ulHeight;
    
    // Set the size of the output buffer
    retVal = m_pOutputBuffer->SetSize(ulNumBytes);
    if (retVal != HXR_OK)
    {
        HX_DELETE(m_pGIFCodec);
        HX_RELEASE(m_pOutputBuffer);
        return retVal;
    }

    // Initially draw the background color into the display buffer
    DrawBackgroundColor();

    // Initialize the rendering members
    m_ulCurImg           = 0;
    m_ulCurImgRenderTime = 0;
    m_ulCurDelayTime     = 0;
    m_lLastImg           = -1;

    // Initialize the loop done count
    m_ulLoopsDone        = 0;

    // Clear the ignore packets flag
    m_bIgnorePackets     = FALSE;

    // Set the first time sync flag
    m_bFirstTimeSync     = TRUE;

    // If the parsing failed, then fill in the image with a bomb image
    if (m_ulRendererFlags & GIF_RENDERER_FLAG_PARSEFAILED)
    {
        CopyBombImage();
    }
    else
    {
        // Send some debug info
        MLOG_MISC(m_pErrorMessages,
                  "0x%08x::OnHeader() w=%lu h=%lu loopcount=%lu\n",
                   this, m_pGIFCodec->GetLogicalScreenWidth(),
                   m_pGIFCodec->GetLogicalScreenHeight(), m_pGIFCodec->GetLoopCount());
    }

    // Get the duration
    pStreamHeaderObj->GetPropertyULONG32("Duration", m_ulEndTime);

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnBegin(UINT32 ulTimeAfterBegin)
{
    MLOG_MISC(m_pErrorMessages,
              "%lu OnBegin(%lu) this=0x%08x\n",
              HX_GET_BETTERTICKCOUNT(), ulTimeAfterBegin, this);

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::GetDisplayType(REF(HX_DISPLAY_TYPE) rDisplayType,
                                          REF(IHXBuffer *)     rpDisplayInfo)
{
    rDisplayType = HX_DISPLAY_WINDOW          |
                   HX_DISPLAY_SUPPORTS_RESIZE |
                   HX_DISPLAY_SUPPORTS_FULLSCREEN;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnPacket(IHXPacket *pPacket, INT32 lTimeOffset)
{
    MLOG_MISC(m_pErrorMessages,
              "%lu OnPacket(,%ld) this=0x%08x\n",
              HX_GET_BETTERTICKCOUNT(), lTimeOffset, this);
    // Check for input error
    if (pPacket == NULL)
    {
        return HXR_INVALID_PARAMETER;
    }

    // Save the time offset
    m_lTimeOffset = lTimeOffset;

    // If we are in between OnPreSeek() and OnPostSeek(), we will ignore 
    // packets
    if (m_bIgnorePackets || m_bImageBombed)
    {
        return HXR_OK;
    }

    // Check if the packet is lost
    if (pPacket->IsLost())
    {
        m_pGIFCodec->PacketLost();
        return HXR_OK;
    }

    // Get the IHXBuffer from the packet
    IHXBuffer *pBuffer = pPacket->GetBuffer();
    if (pBuffer == NULL)
    {
        return HXR_INVALID_PARAMETER;
    }

    BYTE*  pData      = pBuffer->GetBuffer();
    UINT32 ulDataSize = pBuffer->GetSize();

    // Retrieve the flags
    UINT32 ulFlags = 0;
    UnPack32(pData, ulFlags);

    // Is this packet the beginning of a new image?
    BOOL bFirstInImage = FALSE;
    if (ulFlags & 0x01)
    {
        bFirstInImage = TRUE;
    }

    // Comment extensions at the end of the file could mean we receive packets
    // after we are finished decoding. In this case, we just return
    if (m_pGIFCodec->DecompressFinished())
    {
        HX_RELEASE(pBuffer);
        return HXR_OK;
    }

    // Pass the data on to the CGIFCodec
    HX_RESULT retVal = m_pGIFCodec->Decompress(pBuffer->GetBuffer() + 4,
                                               pBuffer->GetSize()   - 4,
                                               bFirstInImage);
    if (retVal != HXR_OK)
    {
        CopyBombImage();
    }

    // Release our reference to the IHXBuffer
    HX_RELEASE(pBuffer);

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnTimeSync(UINT32 ulTime)
{
    MLOG_TIMING(m_pErrorMessages,
              "0x%08x::OnTimeSync(%lu)\n",
              this, ulTime);
    HX_RESULT retVal = HXR_OK;

    if (m_bFirstTimeSync || m_bPaused)
    {
        // Clear the first timesync flag
        m_bFirstTimeSync = FALSE;
        // Clear the paused flag
        m_bPaused = FALSE;
        // Adjust the time according to the
        // offset passed in through OnPacket()
        INT32  lAdjustedTime  = ((INT32) ulTime) + m_lTimeOffset;
        UINT32 ulAdjustedTime = (UINT32) (lAdjustedTime >= 0 ? lAdjustedTime : 0);
        // Set the current scheduler time base
        if (m_pScheduler)
        {
            // Compute time in milliseconds
            m_tSchedulerTimeBase = m_pScheduler->GetCurrentSchedulerTime();
//            m_ulSchedulerTimeBase = (cTime.tv_sec * 1000) + ((cTime.tv_usec + 500) / 1000);
            m_ulTimeAtSchedulerTimeBase = ulAdjustedTime;
        }
        // Update the display
        UpdateDisplay(ulAdjustedTime);
    }

    return retVal;
}

HX_RESULT CGIFRenderer::UpdateDisplay(UINT32 ulTime)
{
    MLOG_MISC(m_pErrorMessages,
              "%lu UpdateDisplay(%lu) this=0x%08x\n",
              HX_GET_BETTERTICKCOUNT(), ulTime, this);

    HX_RESULT retVal = HXR_OK;

    // Do we have a valid CGIFCodec?
    if (m_pGIFCodec && !m_bImageBombed)
    {
        // Reset the damage flag
        BOOL bDoDamage = FALSE;
        // Compute which frame we should be on at this time
        UINT32 ulFrameIndex = 0;
        if (m_pGIFCodec->GetNumImages() > 1)
        {
            // Get the modulo time
            UINT32 ulModTime   = 0;
            UINT32 ulCycleTime = GetCycleTime();
            if (ulCycleTime)
            {
                UINT32 ulIter      = ulTime / ulCycleTime;
                UINT32 ulIterStart = ulIter * ulCycleTime;
                ulModTime          = ulTime - ulIterStart;
            }
            UINT32 ulRunSum    = 0;
            UINT32 ulNumFrames = m_pGIFCodec->GetNumImages();
            for (UINT32 i = 0; i < ulNumFrames; i++)
            {
                // Get the delay for this frame
                UINT32     ulFrameDelay = 0;
                CGIFImage* pImage       = m_pGIFCodec->GetImage(i);
                if (pImage)
                {
                    ulFrameDelay = pImage->GetDelayTime() * 10;
                }
                // Check if our time is in this frame
                if (ulModTime >= ulRunSum &&
                    ulModTime <  ulRunSum + ulFrameDelay)
                {
                    ulFrameIndex = i;
                    break;
                }
                // Add the delay for this frame to the running sum
                ulRunSum += ulFrameDelay;
            }
        }
        // Is the frame currently in the buffer the same
        // as our computed frame? If it is, then we don't
        // need to update our buffer
        BOOL bFinished         = FALSE;
        BOOL bWouldHaveSkipped = FALSE;
        if (ulFrameIndex != m_ulCurFrameIndex)
        {
            // Make sure we don't skip frames
            UINT32 ulFrameIndexNoSkip = ulFrameIndex;
            if (m_ulCurFrameIndex != 0xFFFFFFFF)
            {
                UINT32 ulFrameDiff = 0;
                if (ulFrameIndex >= m_ulCurFrameIndex)
                {
                    ulFrameDiff = ulFrameIndex - m_ulCurFrameIndex;
                }
                else
                {
                    ulFrameDiff = m_pGIFCodec->GetNumImages() -
                                  m_ulCurFrameIndex + ulFrameIndex;
                }
                if (ulFrameDiff > 1)
                {
                    ulFrameIndexNoSkip = m_ulCurFrameIndex + 1;
                    if (ulFrameIndexNoSkip >= m_pGIFCodec->GetNumImages())
                    {
                        ulFrameIndexNoSkip = 0;
                    }
                }
            }
            // Enforce no skipping of frames, but set a flag if
            // we did have to change the frame, so that we can
            // use that to update our callback interval
            if (ulFrameIndexNoSkip != ulFrameIndex)
            {
                // Yes, we are having to force not to skip a frame
                bWouldHaveSkipped = TRUE;
                ulFrameIndex      = ulFrameIndexNoSkip;
            }
            // Is the image we want to draw done? If it's not finished,
            // we DON'T want to draw but we DO want another callback
            CGIFImage* pCurImage = m_pGIFCodec->GetImage(ulFrameIndex);
            if (pCurImage)
            {
                bFinished = pCurImage->Finished();
            }
            // Do we need to update?
            if (bFinished)
            {
                // Yes, we need to update, so first we get the image
                m_pGIFCodec->GetRGBImageEx((m_ulCurFrameIndex == 0xFFFFFFFF ? -1 : (INT32) m_ulCurFrameIndex),
                                           ulFrameIndex,
                                           m_pOutputBuffer->GetBuffer(),
                                           m_pGIFCodec->GetLogicalScreenWidth(),
                                           m_pGIFCodec->GetLogicalScreenHeight(),
                                           m_ulPadWidth,
                                           m_ulBytesPerPixel,
                                           m_bRowsInverted,
                                           m_bRGBOrdering,
                                           m_ulBackgroundColor,
                                           (m_ulMediaOpacity == 255 ? FALSE : TRUE),
                                           m_ulMediaOpacity,
                                           m_bMediaChromaKeySpecified,
                                           m_ulMediaChromaKey,
                                           m_ulMediaChromaKeyTolerance,
                                           m_ulMediaChromaKeyOpacity);
                // XXXMEH - do dumb assignment for now. We should later check
                // chroma key to find if any colors were actually encountered.
                if (m_ulBackgroundOpacity < 255 ||
                    m_ulMediaOpacity < 255      ||
                    m_bMediaChromaKeySpecified)
                {
                    m_bUsesAlphaChannel = TRUE;
                }
                // Set the damage flag
                bDoDamage = TRUE;
                // We need to damage the rect. If we are bltting off
                // of HX_SURFACE_UPDATE2, then we should damage the
                // rect of both the current frame and the previous frame.
                // If we are still bltting off of HX_SURFACE_UPDATE, then
                // we need to damage the whole logical screen area.
                if (m_bCanBltSubRects)
                {
                    // Damage the rect of the previous frame
                    DamageFrameRect(m_ulCurFrameIndex);
                    // Damage the rect of the current frame
                    DamageFrameRect(ulFrameIndex);
                    // Force a redraw
                    if (m_pMISUSSite)
                    {
                        m_pMISUSSite->ForceRedraw();
                    }
                }
                else
                {
                    // Damage entire site area
                    if (m_pMISUSSite)
                    {
                        // Get the size of the site
                        HXxSize cSize = {0, 0};
                        m_pMISUSSite->GetSize(cSize);
                        // Get the damage rect
                        HXxRect cRect = {0, 0, cSize.cx, cSize.cy};
                        MLOG_MISC(m_pErrorMessages,
                                  "%lu     Damaging entire logical screen: "
                                  "(%ld,%ld,%ld,%ld) (%ld x %ld)\n",
                                  HX_GET_BETTERTICKCOUNT(),
                                  cRect.left,  cRect.top,
                                  cRect.right, cRect.bottom,
                                  HXxRECT_WIDTH(cRect),
                                  HXxRECT_HEIGHT(cRect));
                        m_pMISUSSite->DamageRect(cRect);
                        m_pMISUSSite->ForceRedraw();
                    }
                }
                // Check if we just completed a loop. If the frame
                // index that we just drew into the buffer is LOWER
                // than the frame index that USED to be there, then
                // we will assume we just wrapped around
                if ((m_ulCurFrameIndex           == 0xFFFFFFFF &&
                     m_pGIFCodec->GetNumImages() == 1) ||
                    (m_ulCurFrameIndex           != 0xFFFFFFFF &&
                     ulFrameIndex                <  m_ulCurFrameIndex))
                {
                    m_ulLoopsDone++;
                }
                // Update the current frame index
                m_ulCurFrameIndex = ulFrameIndex;
            }
        }

        // Do we need to schedule another callback?
        if (m_bSiteAttached &&                                        // only schedule another callback if site is attached
            m_pCallback    &&                                         // can't do it without a callback object
            (m_bPreserveMediaRepeat ||
             (!m_bPreserveMediaRepeat && m_ulLoopsDone == 0)) &&
            ((bFinished == FALSE)               ||                    // GIF isn't finished yet
            (m_pGIFCodec->GetLoopCount() == 0) ||                     // GIF says to loop infinitely
             (m_pGIFCodec->GetLoopCount() > 0 &&                      // GIF says to loop a finite number of
              m_ulLoopsDone < m_pGIFCodec->GetLoopCount())))          //   times and we're not finished
        {
            // If we just drew the image, then don't schedule a callback until
            // the delay time from now. If we didn't draw, then schedule one
            // kCallbackInterval milliseconds from now
            BOOL       bRelative     = TRUE;
            UINT32     ulTimeFromNow = kCallbackInterval;
            HXTimeval cTime         = {0, 0};
            // Was the frame finished?
            if (bFinished)
            {
                // Did we draw and would have skipped a frame?
                if (bDoDamage && bWouldHaveSkipped)
                {
                    // Schedule a short relative callback
                    bRelative     = TRUE;
                    ulTimeFromNow = kMinCallbackInterval;
                }
                else
                {
                    // Get the time that the next frame would display
                    UINT32 ulNextFrameTime = GetNextFrameTime(ulTime);
                    // If the difference is small enough, then schedule
                    // a short relative callback. Otherwise schedule
                    // an absolute callback
                    if (ulNextFrameTime - ulTime > kMinCallbackInterval)
                    {
                        // Get the time difference between the next frame
                        // time and the time at the scheduler time base
                        UINT32 ulDiff = 0;
                        if (ulNextFrameTime >= m_ulTimeAtSchedulerTimeBase)
                        {
                            ulDiff = ulNextFrameTime - m_ulTimeAtSchedulerTimeBase;
                        }
                        else
                        {
                            ulDiff = 0xFFFFFFFF - m_ulTimeAtSchedulerTimeBase + ulNextFrameTime;
                        }
                        // Compute the difference in seconds and microseconds
                        HXTimeval cDiff = {0, 0};
                        cDiff.tv_sec  = ulDiff / 1000;
                        cDiff.tv_usec = (ulDiff - (cDiff.tv_sec * 1000)) * 1000;
                        // Add this difference to the scheduler time base
                        cTime.tv_sec  = m_tSchedulerTimeBase.tv_sec  + cDiff.tv_sec;
                        cTime.tv_usec = m_tSchedulerTimeBase.tv_usec + cDiff.tv_usec;
                        // Wrap the usec if necessary
                        if (cTime.tv_usec >= 1000000)
                        {
                            cTime.tv_usec -= 1000000;
                            cTime.tv_sec  += 1;
                        }
                        // Clear the relative flag
                        bRelative = FALSE;
                    }
                    else
                    {
                        bRelative     = TRUE;
                        ulTimeFromNow = kMinCallbackInterval;
                    }
                }
            }
            else
            {
                // Frame had not completed decoding - schedule a
                // relative callback
                bRelative     = TRUE;
                ulTimeFromNow = kNotFinishedInterval;
            }
            // Schedule the next callback
            if (bRelative)
            {
                m_pCallback->ScheduleRelativeCallback(ulTimeFromNow);
            }
            else
            {
                m_pCallback->ScheduleAbsoluteCallback(cTime);
            }
        }
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::OnPreSeek(UINT32 ulTimeBefore, UINT32 ulTimeAfter)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::OnPreSeek(%lu,%lu)\n",
              this, ulTimeBefore, ulTimeAfter);

    // Set the ignore packets flag
    m_bIgnorePackets = TRUE;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnPostSeek(UINT32 ulTimeBefore, UINT32 ulTimeAfter)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::OnPostSeek(%lu,%lu)\n",
              this, ulTimeBefore, ulTimeAfter);

    HX_RESULT retVal = HXR_OK;

    if (m_pStreamHeaderBuffer && !m_bImageBombed)
    {
        // Determine if the seek-to time is past our ending time.
        // We only need to blow away everything if we are going to
        // be getting packets all over again. That will happen only
        // if the seek-to time is < our end time.
        if (ulTimeAfter < m_ulEndTime)
        {
            // We are going to start receiving packets all over again, so
            // we need to blow away the CGIFCodec class and start over
            // again. We will need to reinit it with the stream header
            // buffer.
            //
            // Delete the old CGIFCodec
            HX_DELETE(m_pGIFCodec);
            // Create a new one
            m_pGIFCodec = new CGIFCodec();
            if (m_pGIFCodec)
            {
                // Get the stream header pointer
                BYTE* pData = m_pStreamHeaderBuffer->GetBuffer() + m_ulStreamHeaderOffset;
                // Initialize the CGIFCodec for decompression
                retVal = m_pGIFCodec->InitDecompress(m_pStreamHeaderBuffer->GetBuffer() + m_ulStreamHeaderOffset,
                                                     m_pStreamHeaderBuffer->GetSize()   - m_ulStreamHeaderOffset);
                if (SUCCEEDED(retVal))
                {
                    // Initialize the rendering members
                    m_ulCurImg           = 0;
                    m_ulCurImgRenderTime = 0;
                    m_ulCurDelayTime     = 0;
                    m_lLastImg           = -1;
                    // Initialize the loop done count
                    m_ulLoopsDone        = 0;
                    // Clear the ignore packets flag
                    m_bIgnorePackets     = FALSE;
                }
            }
            else
            {
                retVal = HXR_OUTOFMEMORY;
            }
        }
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }
    
    return retVal;
}

STDMETHODIMP CGIFRenderer::OnPause(UINT32 ulTimeBeforePause)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::OnPause(%lu)\n",
              this, ulTimeBeforePause);

    // Set the paused flag
    m_bPaused = TRUE;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnBuffering(UINT32 ulReason, UINT16 usPercentComplete)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::OnBuffering(%lu,%u)\n",
              this, ulReason, usPercentComplete);
    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::OnEndofPackets()
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::OnEndofPackets()\n",
              this);
    return HXR_OK;
}


STDMETHODIMP CGIFRenderer::EndStream()
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::EndStream()\n",
              this);
    // We're finished with the stream, but we may have
    // more rendering to do
    HX_RELEASE(m_pStream);

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::AttachSite(IHXSite *pSite)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::AttachSite(0x%08x)\n",
              this, pSite);

    // Check for input error
    if (pSite == NULL)
    {
        return HXR_INVALID_PARAMETER;
    }

    // Check to see if we alredy have a site interface
    if (m_pMISUSSite)
    {
        return HXR_UNEXPECTED;
    }

    // Save a copy of the IHXSite interface
    m_pMISUSSite = pSite;
    m_pMISUSSite->AddRef();

    // Fill in the size
    m_cSize.cx = m_ulWidth;
    m_cSize.cy = m_ulHeight;

    // Tell the site of our size
    m_pMISUSSite->SetSize(m_cSize);

    // Lets see if we have a new site that supports sub rects.
    IHXSubRectSite* pSubRectSite = NULL;
    if(HXR_OK == m_pMISUSSite->QueryInterface(IID_IHXSubRectSite,
                                              (void**)&pSubRectSite))
    {
        //If so, since IHXSubRectSite inheirits from IHXSite, lets just
        //swap the pointers and sign up for the service.
        HX_RELEASE(m_pMISUSSite);
        m_pMISUSSite = pSubRectSite;
        pSubRectSite->SendSubRectMessages(TRUE);
        // Set the flag
        m_bCanBltSubRects = TRUE;
    }

    // Set the site attached flag
    m_bSiteAttached = TRUE;

    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::DetachSite()
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::DetachSite()\n",
              this);

    if (m_pCallback)
    {
        if (m_pCallback->IsCallbackPending())
        {
            m_pCallback->RemovePendingCallback();
        }
    }
    if (m_pClientAdviseSink)
    {
        m_pClientAdviseSink->Close();
    }
    // Release the callback
    // XXXMEH - MUST DO THIS HERE or we'll have circular
    // dependency and leak CGIFRenderers
    HX_RELEASE(m_pCallback);

    // Release our site interface
    HX_RELEASE(m_pMISUSSite);

    // We're done with these...
    if (m_pMISUS)
    {
        m_pMISUS->ReleaseSingleSiteUser();
    }
    HX_RELEASE(m_pMISUS);

    // Clear the site attached flag
    m_bSiteAttached = FALSE;

    return HXR_OK;
}

STDMETHODIMP_(BOOL) CGIFRenderer::NeedsWindowedSites()
{
    return FALSE;
}

STDMETHODIMP CGIFRenderer::InitializeStatistics(UINT32 ulRegistryID)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::InitializeStatistics(%lu)\n",
              this, ulRegistryID);

    // Add our renderer name to the HXRegistry
    IHXRegistry *pRegistry = NULL;
    HX_RESULT retVal = m_pContext->QueryInterface(IID_IHXRegistry, (void **) &pRegistry);
    if (retVal == HXR_OK)
    {
        // Get the current registry key name
        IHXBuffer *pszRegistryName = NULL;
        retVal                      = pRegistry->GetPropName(ulRegistryID, pszRegistryName);
        if (retVal == HXR_OK)
        {
            // Create an IHXBuffer to hold the name
        
            IHXBuffer* pValue = NULL;

            if (m_pCommonClassFactory)
            {
                IUnknown* pUnknown = NULL;
                m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**)&pUnknown);
                if (pUnknown)
                {
                    pUnknown->QueryInterface(IID_IHXBuffer, (void**)&pValue);
                    HX_RELEASE(pUnknown);
                }
            }
            if (!pValue)
            {
                HX_RELEASE(pszRegistryName);
                HX_RELEASE(pRegistry);
                return HXR_OUTOFMEMORY;
            }
            pValue->AddRef();

            // Create the key name
            char szRegistryEntry[MAX_DISPLAY_NAME] = {0}; /* Flawfinder: ignore */
            SafeSprintf(szRegistryEntry, MAX_DISPLAY_NAME, "%s.name", pszRegistryName->GetBuffer());

            // Set the key value
            retVal = pValue->Set((const BYTE*) m_pszName, strlen(m_pszName) + 1);
            if (retVal != HXR_OK)
            {
                HX_RELEASE(pValue);
                HX_RELEASE(pszRegistryName);
                HX_RELEASE(pRegistry);
                return retVal;
            }

            // Add the key/value pair to the registry
            pRegistry->AddStr(szRegistryEntry, pValue);

            HX_RELEASE(pValue);
            HX_RELEASE(pszRegistryName);
        }
        HX_RELEASE(pRegistry);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::UpdateStatistics()
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::UpdateStatistics()\n",
              this);
    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::SetPropertyULONG32(const char* pName, ULONG32 ulVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        // Clear the update needed flag
        BOOL bUpdateNeeded = FALSE;
        if (!strcmp(pName, "backgroundOpacity"))
        {
            if (ulVal > 255) ulVal = 255;
            if (ulVal != m_ulBackgroundOpacity) bUpdateNeeded = TRUE;
            m_ulBackgroundOpacity = ulVal;
        }
        else if (!strcmp(pName, "mediaOpacity"))
        {
            if (ulVal > 255) ulVal = 255;
            if (ulVal != m_ulMediaOpacity) bUpdateNeeded = TRUE;
            m_ulMediaOpacity = ulVal;
        }
        else if (!strcmp(pName, "chromaKey"))
        {
            if (ulVal != m_ulMediaChromaKey) bUpdateNeeded = TRUE;
            m_ulMediaChromaKey         = ulVal;
            m_bMediaChromaKeySpecified = TRUE;
        }
        else if (!strcmp(pName, "chromaKeyTolerance"))
        {
            if (ulVal != m_ulMediaChromaKeyTolerance) bUpdateNeeded = TRUE;
            m_ulMediaChromaKeyTolerance = ulVal;
        }
        else if (!strcmp(pName, "chromaKeyOpacity"))
        {
            if (ulVal != m_ulMediaChromaKeyOpacity) bUpdateNeeded = TRUE;
            m_ulMediaChromaKeyOpacity = ulVal;
        }
        // Did any values change?
        if (bUpdateNeeded)
        {
            // If we have not seen our first time sync yet, then
            // there is no need to update the buffer, since the first
            // copy has not been done yet
            if (!m_bFirstTimeSync && m_pGIFCodec)
            {
                // Compute the current frame we are on (note that
                // m_ulCurImg contains the index of the NEXT frame
                // that we are about to draw
                UINT32 ulCurFrame = (m_ulCurImg > 0 ? m_ulCurImg - 1 : 0);
                // Update the buffer
                // XXXMEH - optimization for later. In some cases if we 
                // just changed the media opacity, then it may not be necessary
                // to start back at the beginning and rebuild the frame buffer
                // from the first frame.
                m_pGIFCodec->GetRGBImageEx(-1,
                                           ulCurFrame,
                                           m_pOutputBuffer->GetBuffer(),
                                           m_pGIFCodec->GetLogicalScreenWidth(),
                                           m_pGIFCodec->GetLogicalScreenHeight(),
                                           m_ulPadWidth,
                                           m_ulBytesPerPixel,
                                           m_bRowsInverted,
                                           m_bRGBOrdering,
                                           m_ulBackgroundColor,
                                           (m_ulMediaOpacity == 255 ? FALSE : TRUE),
                                           m_ulMediaOpacity,
                                           m_bMediaChromaKeySpecified,
                                           m_ulMediaChromaKey,
                                           m_ulMediaChromaKeyTolerance,
                                           m_ulMediaChromaKeyOpacity);
                // XXXMEH - do dumb assignment for now. We should later check
                // chroma key to find if any colors were actually encountered.
                if (m_ulBackgroundOpacity < 255 ||
                    m_ulMediaOpacity < 255      ||
                    m_bMediaChromaKeySpecified)
                {
                    m_bUsesAlphaChannel = TRUE;
                }
            }
        }
        retVal = m_pValues->SetPropertyULONG32(pName, ulVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetPropertyULONG32(const char* pName, REF(ULONG32) rulVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyULONG32(pName, rulVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetFirstPropertyULONG32(REF(const char*) rpName, REF(ULONG32) rulVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyULONG32(rpName, rulVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetNextPropertyULONG32(REF(const char*) rpName, REF(ULONG32) rulVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyULONG32(rpName, rulVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::SetPropertyBuffer(const char* pName, IHXBuffer* pVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->SetPropertyBuffer(pName, pVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetPropertyBuffer(const char* pName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyBuffer(pName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetFirstPropertyBuffer(REF(const char*) rpName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyBuffer(rpName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetNextPropertyBuffer(REF(const char*) rpName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyBuffer(rpName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::SetPropertyCString(const char* pName, IHXBuffer* pVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        // Check for the default SMIL namespace
        BOOL bUpdateNeeded = FALSE;
        if (!strcmp(pName, "SMILDefaultNamespace"))
        {
            if (m_ulBackgroundOpacity != 0)
            {
                bUpdateNeeded = TRUE;
            }
            m_ulBackgroundOpacity = 0;
            // Update the background color
            UINT32 ulBgAlpha    = 255 - m_ulBackgroundOpacity;
            m_ulBackgroundColor = (m_ulBackgroundColor & 0x00FFFFFF) |
                                  ((ulBgAlpha << 24)   & 0xFF000000);
        }
        // Do we need to update the display buffer
        if (bUpdateNeeded && !m_bImageBombed)
        {
            // If we haven't received our first time sync yet
            // (m_bFirstTimeSync == TRUE), then all we need to do
            // is redraw the transparent background color into the
            // the display buffer. If we have already received our
            // first time sync, then we need to redraw the entire buffer,
            // including redrawing the frames
            if (m_bFirstTimeSync)
            {
                // We haven't draw any frames into our display buffer
                // yet, so all we have to do is redraw a transparent
                // background color into the buffer.
                DrawBackgroundColor();
                // We definitely will be using the alpha channel
                m_bUsesAlphaChannel = TRUE;
            }
            else
            {
                if (m_pGIFCodec && m_pOutputBuffer)
                {
                    // Compute the current frame we are on (note that
                    // m_ulCurImg contains the index of the NEXT frame
                    // that we are about to draw
                    UINT32 ulCurFrame = (m_ulCurImg > 0 ? m_ulCurImg - 1 : 0);
                    // Update the buffer
                    // XXXMEH - optimization for later. In some cases if we 
                    // just changed the media opacity, then it may not be necessary
                    // to start back at the beginning and rebuild the frame buffer
                    // from the first frame.
                    m_pGIFCodec->GetRGBImageEx(-1,
                                               ulCurFrame,
                                               m_pOutputBuffer->GetBuffer(),
                                               m_pGIFCodec->GetLogicalScreenWidth(),
                                               m_pGIFCodec->GetLogicalScreenHeight(),
                                               m_ulPadWidth,
                                               m_ulBytesPerPixel,
                                               m_bRowsInverted,
                                               m_bRGBOrdering,
                                               m_ulBackgroundColor,
                                               (m_ulMediaOpacity == 255 ? FALSE : TRUE),
                                               m_ulMediaOpacity,
                                               m_bMediaChromaKeySpecified,
                                               m_ulMediaChromaKey,
                                               m_ulMediaChromaKeyTolerance,
                                               m_ulMediaChromaKeyOpacity);
                    // XXXMEH - do dumb assignment for now. We should later check
                    // chroma key to find if any colors were actually encountered.
                    if (m_ulBackgroundOpacity < 255 ||
                        m_ulMediaOpacity < 255      ||
                        m_bMediaChromaKeySpecified)
                    {
                        m_bUsesAlphaChannel = TRUE;
                    }
                }
            }
        }
        retVal = m_pValues->SetPropertyCString(pName, pVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetPropertyCString(const char* pName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetPropertyCString(pName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetFirstPropertyCString(REF(const char*) rpName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetFirstPropertyCString(rpName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::GetNextPropertyCString(REF(const char*) rpName, REF(IHXBuffer*) rpVal)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pValues)
    {
        retVal = m_pValues->GetNextPropertyCString(rpName, rpVal);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::UpdatePacketTimeOffset(INT32 lTimeOffset)
{
    HX_RESULT retVal = HXR_OK;

    // Save the time offset
    // XXXMEH - should this be negative
    m_lTimeOffset = -lTimeOffset;

    return retVal;
}

/************************************************************************
 *	Method:
 *	    IHXUpdateProperties::UpdatePlayTimes
 *	Purpose:
 *	    Call this method to update the playtime attributes
 */
STDMETHODIMP
CGIFRenderer::UpdatePlayTimes(IHXValues* pProps)
{
    return HXR_OK;
}

STDMETHODIMP CGIFRenderer::HandleCallback(UINT32 ulSchedulerTime, UINT32 ulInstance)
{
    HX_RESULT retVal = HXR_OK;
    
    if (!m_bPaused)
    {
        // Get the current scheduler time
        HXTimeval cTime = m_pScheduler->GetCurrentSchedulerTime();
        // Compute the difference in scheduler time
        UINT32 ulSchedDiff = GetTimevalDiff(m_tSchedulerTimeBase, cTime);
        // Compute the time in our timeline
        UINT32 ulTime = m_ulTimeAtSchedulerTimeBase + ulSchedDiff;
        // Update the display (if necessary)
        retVal = UpdateDisplay(ulTime);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPosLength(UINT32 ulPosition, UINT32 ulLength)
{
    HX_RESULT retVal = HXR_OK;

    // XXXMEH
//    char szDbgStr[128];
//    DEBUGPRINTF(szDbgStr, "CGIFRenderer::CASOnPosLength(%lu,%lu) tick=%lu\n",
//                ulPosition, ulLength, HX_GET_BETTERTICKCOUNT());

    if (m_bPaused)
    {
        // Clear the paused flag
        m_bPaused = FALSE;
        // Adjust the time according to the
        // offset passed in through OnPacket()
        INT32  lAdjustedTime  = ((INT32) ulPosition) + m_lTimeOffset;
        UINT32 ulAdjustedTime = (UINT32) (lAdjustedTime >= 0 ? lAdjustedTime : 0);
        // Set the current scheduler time base
        if (m_pScheduler)
        {
            // Compute time in milliseconds
            m_tSchedulerTimeBase = m_pScheduler->GetCurrentSchedulerTime();
            m_ulTimeAtSchedulerTimeBase = ulAdjustedTime;
        }
        // Update the display
        UpdateDisplay(ulAdjustedTime);
    }

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPresentationOpened()
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPresentationClosed()
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnStatisticsChanged()
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPreSeek(ULONG32 ulOldTime, ULONG32 ulNewTime)
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPostSeek(ULONG32 ulOldTime, ULONG32 ulNewTime)
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnStop()
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnPause(ULONG32 ulTime)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::CASOnPause(%lu)\n",
              this, ulTime);
    HX_RESULT retVal = HXR_OK;

    m_bPaused = TRUE;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnBegin(ULONG32 ulTime)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x::CASOnBegin(%lu)\n",
              this, ulTime);
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnBuffering(ULONG32 ulFlags, UINT16 unPercentComplete)
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

STDMETHODIMP CGIFRenderer::CASOnContacting(const char* pHostName)
{
    HX_RESULT retVal = HXR_OK;

    return retVal;
}

void CGIFRenderer::OnMouseMove(INT16 fwKeys, INT16 xPos, INT16 yPos)
{
    // Make sure we're up and running
    if (!m_pGIFCodec)
    {
        return;
    }

    // don't do anything if the x/y coordinates have changed from the
    // last call to OnMouseMove - this is needed because the call to
    // IHXStatusMessage::SetStatus() results in a WM_MOUSEMOVE event
    if(xPos == m_sOldMouseX && yPos == m_sOldMouseY)
    {
        return;
    }
    m_sOldMouseX = xPos;
    m_sOldMouseY = yPos;


#if defined(_WINDOWS)
    HCURSOR hCurrentCursor = GetCursor();
#endif

    if (xPos >= 0 && xPos < (INT16) m_pGIFCodec->GetLogicalScreenWidth()  &&
        yPos >= 0 && yPos < (INT16) m_pGIFCodec->GetLogicalScreenHeight() &&
        m_cURL.length() > 0) // we have a link
    {
        // We ARE over a hyperlink
#if defined(_WINDOWS)
        if(!m_hHyperlinkCursor)
        {
            m_hHyperlinkCursor = LoadCursor(g_hInstance, MAKEINTRESOURCE(HANDCURSOR));
            if(!m_hHyperlinkCursor)
            {
                m_hHyperlinkCursor = LoadCursor(NULL, IDC_UPARROW);
            }
        }

        if(m_hHyperlinkCursor && hCurrentCursor != m_hHyperlinkCursor)
        {
            // We're over a link and the cursor is NOT already the hyperlink cursor,
            // so change it. This will happen when we get a WM_SETCURSOR event
            m_bSetHyperlinkCursor = TRUE;
        }
#elif defined(_MACINTOSH)
        if (m_hHyperlinkCursor)
        {
            ::SetCursor(*m_hHyperlinkCursor);
            m_eCurrentCursor = CURSOR_HYPERLINK;
        }
#elif defined(_UNIX) && defined(USE_XWINDOWS)
    if (m_hCurrentCursor == -1 && m_pDisplay && m_Window && m_hHyperlinkCursor != -1)
    {
	XLockDisplay(m_pDisplay);
        XDefineCursor(m_pDisplay, m_Window, m_hHyperlinkCursor);
	XUnlockDisplay(m_pDisplay);
        m_hCurrentCursor = m_hHyperlinkCursor;
    }
#endif
    if (m_pStatusMessage)
    {
	m_bStatusMsgWillNeedErasing = TRUE;
        m_pStatusMessage->SetStatus(m_cURL.c_str());
    }
    }
    else
    {
        // We are NOT over a hyperlink
#if defined(_WINDOWS)
        if(hCurrentCursor == m_hHyperlinkCursor)
        {
            // We are not over a hyperlink and out cursor IS the hyperlink cursor,
            // so we need to change it back. This will happen when we get a WM_SETCURSOR event
            m_bSetHyperlinkCursor = FALSE;
        }
#elif defined(_MACINTOSH)
        if (m_eCurrentCursor == CURSOR_HYPERLINK)
        {
            ::InitCursor();
            m_eCurrentCursor = CURSOR_ARROW;
        }
#elif defined(_UNIX) && defined(USE_XWINDOWS)
    if (m_hCurrentCursor == m_hHyperlinkCursor)
    {
        if (m_pDisplay && m_Window)
        {
	XLockDisplay(m_pDisplay);
        XUndefineCursor(m_pDisplay, m_Window);
	XUnlockDisplay(m_pDisplay);
        m_hCurrentCursor = -1;
        }
    }
#endif
	if (m_pStatusMessage  &&
		// /Fixes PR 65008 (GIF version): only set this to NULL if we
		// have recently set the status message, otherwise we may
		// cause SMIL's setting of the status message to be
		// overwritten with NULL, i.e., erased:
		m_bStatusMsgWillNeedErasing)
	{
	    m_bStatusMsgWillNeedErasing = FALSE;
	    m_pStatusMessage->SetStatus(NULL);
	}
    }
}

STDMETHODIMP CGIFRenderer::HandleEvent(HXxEvent* pEvent)
{
    HX_RESULT retVal = HXR_OK;

    if (pEvent)
    {
        // Set defaults
        pEvent->handled = FALSE;
        pEvent->result  = 0;
        // Switch based on event type
        switch (pEvent->event)
        {
            case HX_SURFACE_UPDATE:
                {
                    if (m_pOutputBuffer)
                    {
                        IHXVideoSurface *pSurface = (IHXVideoSurface *) (pEvent->param1);
                        HXxSize           size;
                        m_pMISUSSite->GetSize(size);
                        DrawToRMASurface(pSurface, 0, 0, size);
                    }
#if defined(_UNIX) && defined(USE_XWINDOWS)
                    {
                        HXxWindow *pWnd = (HXxWindow*)pEvent->param2;
                        if (pWnd) 
                        {
                            m_pDisplay = (Display*)pWnd->display;
                            m_Window = (Window)pWnd->window;
                            if (m_pDisplay && m_hHyperlinkCursor == -1)
			    {
				    XLockDisplay(m_pDisplay);
				    m_hHyperlinkCursor = XCreateFontCursor(m_pDisplay, 
                                                   XC_hand2);
				    XUnlockDisplay(m_pDisplay);
			    }
                        }
                    }
#endif
                    pEvent->handled = TRUE;
                }
                break;

            case HX_SURFACE_UPDATE2:
                {
                   HXxExposeInfo* pExpose = (HXxExposeInfo*)pEvent->param2;
                   IHXSubRectVideoSurface* pSurface =
                       (IHXSubRectVideoSurface*) pEvent->param1;
                   if (pSurface)
                   {
                       pSurface->AddRef();
                       RMASurfaceUpdate2(pSurface,
                                         &pExpose->extents,
                                         pExpose->pRegion);
                       pSurface->Release();
                   }
                   pEvent->handled = TRUE;           
                }
                break;

            case HX_MOUSE_ENTER:
            case HX_MOUSE_LEAVE:
            case HX_MOUSE_MOVE:
                {
                    HXxPoint* mousePt = (HXxPoint*) pEvent->param1;
                    OnMouseMove (0, (INT16)mousePt->x, (INT16)mousePt->y);
                    pEvent->handled = TRUE;
                }
                break;

            case HX_PRIMARY_BUTTON_UP:
                {
#if defined(HELIX_FEATURE_HYPER_NAVIGATE)
                    HandleClick();
#endif // (HELIX_FEATURE_HYPER_NAVIGATE)
                    pEvent->handled = TRUE;
                }
                break;

#ifdef _WINDOWS
            case WM_SETCURSOR:
                {
                    if(m_bSetHyperlinkCursor)
                    {
                        pEvent->handled       = TRUE;
                        m_hPreHyperlinkCursor = SetCursor(m_hHyperlinkCursor);
                    }
                    else
                    {
                        // pngui will handle the setting of the cursor (back to arrow cursor)
                        pEvent->handled       = FALSE;
                    }
                }
                break;
#endif
            default:
                break;
        }
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

void CGIFRenderer::DrawToRMASurface(IHXVideoSurface *pVideoSurface, UINT32 ulX, UINT32 ulY, const HXxSize &size)
{
#if defined(HELIX_FEATURE_GIF_BROKENIMAGE)
    if (m_bImageBombed && m_bNoNativeSize &&
        (size.cx != (INT32) m_ulWidth ||
         size.cy != (INT32) m_ulHeight))
    {
        // Copy the bomb image
        PXMakeBombImage* pMakeBomb = new PXMakeBombImage();
        if (pMakeBomb)
        {
            PXImage*  pImage = NULL;
            HX_RESULT rv     = pMakeBomb->MakeBombImage(m_pContext,
                                                        (UINT32) size.cx,
                                                        (UINT32) size.cy,
                                                        m_ulBackgroundColor,
                                                        pImage);
            if (SUCCEEDED(rv))
            {
                IHXBuffer* pImageStore = NULL;
                rv = pImage->GetImageStore(&pImageStore);
                if (SUCCEEDED(rv))
                {
                    m_ulWidth  = (UINT32) size.cx;
                    m_ulHeight = (UINT32) size.cy;
                    HX_RELEASE(m_pOutputBuffer);
                    m_pOutputBuffer = pImageStore;
                    m_pOutputBuffer->AddRef();
                }
                HX_RELEASE(pImageStore);
            }
            HX_RELEASE(pImage);
        }
        HX_DELETE(pMakeBomb);
    }
#endif /* #if defined(HELIX_FEATURE_GIF_BROKENIMAGE) */
    if (m_pOutputBuffer)
    {
        pVideoSurface->AddRef();

        HXxRect rDestRect = { 0, 0, size.cx, size.cy};
        HXxRect rSrcRect  = { 0, 0, m_ulWidth,
                                    m_ulHeight };

        HXBitmapInfoHeader cHeader;

        cHeader.biSize          = 40;
        cHeader.biWidth         = (INT32) m_ulWidth;
        cHeader.biHeight        = (INT32) m_ulHeight;
        cHeader.biPlanes        = 1;
        cHeader.biBitCount      = 32;
        cHeader.biCompression   = (m_bUsesAlphaChannel ? ARGB_CID : HX_RGB);
        cHeader.biSizeImage     = 0;
        cHeader.biXPelsPerMeter = 0;
        cHeader.biYPelsPerMeter = 0;
        cHeader.biClrUsed       = 0;
        cHeader.biClrImportant  = 0;

        pVideoSurface->Blt(m_pOutputBuffer->GetBuffer(),
                           &cHeader,
                           rDestRect,
                           rSrcRect);

        HX_RELEASE(pVideoSurface);
    }
}

HX_RESULT CGIFRenderer::RMASurfaceUpdate2(IHXSubRectVideoSurface* pSurface,
                                          HXxRect*                 pExtents,
                                          HXxBoxRegion*              pDirtyRegion)
{
    MLOG_MISC(m_pErrorMessages,
              "%lu RMASurfaceUpdate2() this=0x%08x\n",
              HX_GET_BETTERTICKCOUNT(), this);
    HX_RESULT retVal = HXR_OK;

    if (pSurface && m_pMISUSSite && m_pOutputBuffer)
    {
        // Get the size of the site
        HXxSize size;
        m_pMISUSSite->GetSize(size);
        // Scale dirty rects.
        float fx = (float) m_ulWidth  / (float) size.cx;
        float fy = (float) m_ulHeight / (float) size.cy;
        // Allocate space for the scaled subrects
        HXBOX* pSrcRects = new HXBOX[pDirtyRegion->numRects];
        if (pSrcRects)
        {
            // Go through each rect in the dirty region and scale it to 
            // generate the src rects.
            INT32 i = 0;
            for(i = 0; i < pDirtyRegion->numRects; i++)
            {
                pSrcRects[i].x1 = (float) pDirtyRegion->rects[i].x1 * fx + 0.5;
                pSrcRects[i].x2 = (float) pDirtyRegion->rects[i].x2 * fx + 0.5;
                pSrcRects[i].y1 = (float) pDirtyRegion->rects[i].y1 * fy + 0.5;
                pSrcRects[i].y2 = (float) pDirtyRegion->rects[i].y2 * fy + 0.5;
            }
            // Set up Src region.
            HXxBoxRegion srcRegion;
            srcRegion.numRects = pDirtyRegion->numRects;
            srcRegion.rects    = pSrcRects;
            // Set the values in the bitmap info header
            HXBitmapInfoHeader cHeader;
            cHeader.biSize          = 40;
            cHeader.biWidth         = m_ulWidth;
            cHeader.biHeight        = m_ulHeight;
            cHeader.biPlanes        = 1;
            cHeader.biBitCount      = 32;
            cHeader.biCompression   = (m_bUsesAlphaChannel ? ARGB_CID : HX_RGB);
            cHeader.biSizeImage     = 0;
            cHeader.biXPelsPerMeter = 0;
            cHeader.biYPelsPerMeter = 0;
            cHeader.biClrUsed       = 0;
            cHeader.biClrImportant  = 0;
            cHeader.rcolor          = 0;
            cHeader.gcolor          = 0;
            cHeader.bcolor          = 0;
            // Blit to the video surface
            MLOG_MISC(m_pErrorMessages,
                      "    numRects=%ld\n",
                      srcRegion.numRects);
            for (i = 0; i < srcRegion.numRects; i++)
            {
                MLOG_MISC(m_pErrorMessages,
                          "        rect[%ld] = (%d,%d,%d,%d)\n",
                          i,
                          srcRegion.rects[i].x1,
                          srcRegion.rects[i].y1,
                          srcRegion.rects[i].x2,
                          srcRegion.rects[i].y2);
            }
            pSurface->BltSubRects(m_pOutputBuffer->GetBuffer(),
                                  &cHeader,
                                  pDirtyRegion,
                                  &srcRegion,
                                  1.0/fx, 1.0/fy);
        }
        HX_VECTOR_DELETE(pSrcRects);
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

void CGIFRenderer::HandleClick()
{
#if defined(HELIX_FEATURE_HYPER_NAVIGATE)
    if (m_pHyperNavigate && m_cURL.length() > 0)
    {
        m_pHyperNavigate->GoToURL(m_cURL.c_str(), (m_ucTarget == kTargetPlayer) ? "_player" : (const char*) NULL);
    }
#endif // (HELIX_FEATURE_HYPER_NAVIGATE)
}

HX_RESULT CGIFRenderer::CopyBombImage()
{
    HX_RESULT retVal = HXR_OK;

#if defined(HELIX_FEATURE_GIF_BROKENIMAGE)
    // Set the image bombed flag
    m_bImageBombed  = TRUE;
    // Copy the bomb image
    PXMakeBombImage* pMakeBomb = new PXMakeBombImage();
    if (pMakeBomb)
    {
        PXImage* pImage = NULL;
        retVal          = pMakeBomb->MakeBombImage(m_pContext,
                                                   m_ulWidth,
                                                   m_ulHeight,
                                                   m_ulBackgroundColor,
                                                   pImage);
        if (SUCCEEDED(retVal))
        {
            IHXBuffer* pImageStore = NULL;
            retVal = pImage->GetImageStore(&pImageStore);
            if (SUCCEEDED(retVal))
            {
		UINT32 ulSafeSizeToCopy = pImageStore->GetSize();
		UINT32 ulOutputBufSize = m_pOutputBuffer->GetSize();
		if (ulSafeSizeToCopy > ulOutputBufSize)
		{
		    ulSafeSizeToCopy = ulOutputBufSize;
		}
                memcpy(m_pOutputBuffer->GetBuffer(), /* Flawfinder: ignore */
                       pImageStore->GetBuffer(),
                       ulSafeSizeToCopy);
            }
            HX_RELEASE(pImageStore);
        }
        HX_RELEASE(pImage);
    }
    else
    {
        retVal = HXR_OUTOFMEMORY;
    }
    HX_DELETE(pMakeBomb);
#endif /* #if defined(HELIX_FEATURE_GIF_BROKENIMAGE) */

    return retVal;
}

void CGIFRenderer::DrawBackgroundColor()
{
    if (m_pOutputBuffer)
    {
        // Combine background color and background opacity
        UINT32 ulBgAlpha    = 255 - m_ulBackgroundOpacity;
        m_ulBackgroundColor = (m_ulBackgroundColor & 0x00FFFFFF) |
                              ((ulBgAlpha << 24)   & 0xFF000000);
        // Fill the output buffer with the background color
        UINT32  ulNumPixels  = m_ulWidth * m_ulHeight;
        UINT32* pDst         = (UINT32*) m_pOutputBuffer->GetBuffer();
        while (ulNumPixels--)
        {
            *pDst++ = m_ulBackgroundColor;
        }
    }
}

UINT32 CGIFRenderer::GetCycleTime()
{
    UINT32 ulRet = 0;

    if (m_pGIFCodec)
    {
        UINT32 ulNumFrames = m_pGIFCodec->GetNumImages();
        for (UINT32 i = 0; i < ulNumFrames; i++)
        {
            CGIFImage* pFrame = m_pGIFCodec->GetImage(i);
            if (pFrame)
            {
                // Get delay in ms
                UINT32 ulFrameDelay = pFrame->GetDelayTime() * 10;
                // Add this delay to the total
                ulRet += ulFrameDelay;
            }
        }
    }

    return ulRet;
}

UINT32 CGIFRenderer::GetNextFrameTime(UINT32 ulTime)
{
    UINT32 ulRet = ulTime;

    if (m_pGIFCodec)
    {
        UINT32 ulCycleTime = GetCycleTime();
        if (ulCycleTime)
        {
            // Get the modulo time
            UINT32 ulIter      = ulTime / ulCycleTime;
            UINT32 ulIterStart = ulIter * ulCycleTime;
            UINT32 ulModTime   = ulTime - ulIterStart;
            // Now see which frame is next after this time
            UINT32 ulNumFrames     = m_pGIFCodec->GetNumImages();
            UINT32 ulCurFrameStart = 0;
            for (UINT32 i = 0; i < ulNumFrames; i++)
            {
                CGIFImage* pFrame = m_pGIFCodec->GetImage(i);
                if (pFrame)
                {
                    // Get this frame's delay in ms
                    UINT32 ulFrameDelay = pFrame->GetDelayTime() * 10;
                    // Get the ending time of this frame
                    UINT32 ulCurFrameEnd = ulCurFrameStart + ulFrameDelay;
                    // Is this time in this frame's range?
                    if (ulModTime >= ulCurFrameStart &&
                        ulModTime <  ulCurFrameEnd)
                    {
                        // We found the right frame, so make 
                        // sure and add back in the time that this
                        // iteration started.
                        ulRet = ulIterStart + ulCurFrameEnd;
                        break;
                    }
                    // Add the delay of this frame to the running sum
                    ulCurFrameStart += ulFrameDelay;
                }
            }
        }
    }

    return ulRet;
}

UINT32 CGIFRenderer::GetTimevalDiff(HXTimeval t1, HXTimeval t2)
{
    UINT32 ulRet = 0;

    // Compute the sec diff with 32-bit wrap
    HXTimeval cDiff = {0, 0};
    if (t2.tv_sec >= t1.tv_sec)
    {
        cDiff.tv_sec = t2.tv_sec - t1.tv_sec;
    }
    else
    {
        cDiff.tv_sec = 0xFFFFFFFF - t2.tv_sec + t1.tv_sec;
    }
    // Compute the usec diff
    if (t2.tv_usec >= t1.tv_usec)
    {
        cDiff.tv_usec = t2.tv_usec - t1.tv_usec;
    }
    else
    {
        // We have to borrow from the sec diff
        if (cDiff.tv_sec > 0)
        {
            cDiff.tv_usec = 1000000 + t2.tv_usec - t1.tv_usec;
            cDiff.tv_sec  -= 1;
        }
    }
    // Convert diff to milliseconds
    ulRet = (cDiff.tv_sec * 1000) + ((cDiff.tv_usec + 500) / 1000);

    return ulRet;
}

HX_RESULT STDAPICALLTYPE CGIFRenderer::HXCreateInstance(IUnknown** ppIUnknown)
{
    HX_RESULT retVal = HXR_OK;

    if (ppIUnknown)
    {
        // Set default
        *ppIUnknown = NULL;
        // Create the object
        CGIFRenderer *pObj = new CGIFRenderer();
        if (pObj)
        {
            // QI for IUnknown
            retVal = pObj->QueryInterface(IID_IUnknown, (void**) ppIUnknown);
        }
        else
        {
            retVal = HXR_OUTOFMEMORY;
        }
        if (FAILED(retVal))
        {
            HX_DELETE(pObj);
        }
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return HXR_OK;
}

void CGIFRenderer::DamageFrameRect(UINT32 i)
{
    if (m_pGIFCodec &&
        m_pMISUSSite &&
        i < m_pGIFCodec->GetNumImages())
    {
        CGIFImage* pImg = m_pGIFCodec->GetImage(i);
        if (pImg)
        {
            // Get the rect for the frame
            HXxRect cRect;
            cRect.left   = (INT32) pImg->GetImageLeft();
            cRect.top    = (INT32) pImg->GetImageTop();
            cRect.right  = cRect.left + (INT32) pImg->GetImageWidth();
            cRect.bottom = cRect.top  + (INT32) pImg->GetImageHeight();
            // Get the size of the logical screen
            HXxSize cLSSize = {0, 0};
            cLSSize.cx = (INT32) m_pGIFCodec->GetLogicalScreenWidth();
            cLSSize.cy = (INT32) m_pGIFCodec->GetLogicalScreenHeight();
            // Get the current size of the site
            HXxSize cSiteSize = {0, 0};
            m_pMISUSSite->GetSize(cSiteSize);
            // If the current size of the site is not the
            // same as the logical screen, then we need to scale the
            // damage rect
            if (cSiteSize.cx != cLSSize.cx ||
                cSiteSize.cy != cLSSize.cy)
            {
                // Scale the damage rect
                cRect.left   = cRect.left   * cSiteSize.cx / cLSSize.cx;
                cRect.top    = cRect.top    * cSiteSize.cy / cLSSize.cy;
                cRect.right  = cRect.right  * cSiteSize.cx / cLSSize.cx;
                cRect.bottom = cRect.bottom * cSiteSize.cy / cLSSize.cy;
            }
            // Damage this rect
            MLOG_MISC(m_pErrorMessages,
                      "\tDamaging Frame %lu: (%ld,%ld,%ld,%ld) (%ld x %ld)\n",
                      i, cRect.left, cRect.top, cRect.right, cRect.bottom,
                      HXxRECT_WIDTH(cRect), HXxRECT_HEIGHT(cRect));
            m_pMISUSSite->DamageRect(cRect);
        }
    }
}

