/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: pxjpgrnd.cpp,v 1.4.22.1 2004/07/09 01:53:52 hubbe 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 "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 "hxprefs.h"
#include "hxerror.h"

// pncont
#include "hxbuffer.h"
#include "hxstring.h"

// pnmisc
#include "unkimp.h"
#include "baseobj.h"

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

// baserend
#include "baserend.h"
#include "vbasernd.h"

// pxcomlib
#include "glist.h"
#include "gstring.h"
#include "pxutil.h"
#include "pxcolor.h"
#include "parseurl.h"
#include "pxtransp.h"

// jpeglib
#include "ijglwrap.h"

// pxjpgrnd
#include "gimage.h"
#include "pxjpmlog.h"
#include "pxjpgrnd.h"
#include "jpgrender.ver"

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

const char* const CJPEGRenderer::m_pszName        = "JPEG";
const char* const CJPEGRenderer::m_pszDescription = "Helix JPEG Renderer Plugin";
const char* const CJPEGRenderer::m_ppszMimeType[] = {"application/vnd.rn-jpegstream", NULL};

CJPEGRenderer::CJPEGRenderer() : CRNVisualBaseRenderer()
{
    MLOG_LEAK("0x%08x:CJPEGRenderer::CJPEGRenderer()\n", this);
    m_ulImageWidth              = 0;
    m_ulImageHeight             = 0;
    m_pDecomp                   = NULL;
    m_pImage                    = NULL;
    m_bFirstPacket              = FALSE;
    m_lTimeOffset               = 0;
    m_ulTotalPackets            = 0;
    m_ulNumPackets              = 0;
    m_bValidImage               = FALSE;
    m_ulTarget                  = kTargetBrowser;
    m_pURL                      = NULL;
    m_ulDisplayTime             = 0;
    m_bPastDisplayTime          = FALSE;
    m_ulMediaOpacity            = 255;
    m_bMediaOpacitySpecified    = FALSE;
    m_ulMediaChromaKey          = 0;
    m_bMediaChromaKeySpecified  = FALSE;
    m_ulMediaChromaKeyTolerance = 0;
    m_ulMediaChromaKeyOpacity   = 0;
    m_bUsesAlphaChannel         = FALSE;
    m_bPendingTransparencyBlt   = FALSE;
}

CJPEGRenderer::~CJPEGRenderer()
{
    MLOG_LEAK("0x%08x:CJPEGRenderer::~CJPEGRenderer()\n", this);
    HX_DELETE(m_pDecomp);
    HX_RELEASE(m_pImage);
    HX_DELETE(m_pURL);
}

STDMETHODIMP CJPEGRenderer::QueryInterface(REFIID riid, void** ppvObj)
{
    // If we ever have any new interfaces to add which
    // are specific to the jpeg renderer, then we'll need
    // to add them here. Until then we simply proxy the
    // visual base renderer.
    return CRNVisualBaseRenderer::QueryInterface(riid, ppvObj);
}

STDMETHODIMP_(UINT32) CJPEGRenderer::AddRef()
{
    return CRNVisualBaseRenderer::AddRef();
}


STDMETHODIMP_(UINT32) CJPEGRenderer::Release()
{
    return CRNVisualBaseRenderer::Release();
}

STDMETHODIMP CJPEGRenderer::OnHeader(IHXValues *pHeader)
{
    // Check for input error
    if (!pHeader)
    {
        return HXR_FAIL;
    }

    // Check presentation stream and content versions
    HX_RESULT retVal = CheckStreamVersions(pHeader); // XXXMEH - base renderer
    if (FAILED(retVal))
    {
        AddMimeToUpgradeCollection(m_ppszMimeType[0]); // XXXMEH - base renderer
        return HXR_FAIL;
    }

    // Get the opaque data buffer
    IHXBuffer *pOpaqueData = NULL;
    retVal                  = pHeader->GetPropertyBuffer("OpaqueData", pOpaqueData);
    if (FAILED(retVal))
    {
        return HXR_FAIL;
    }

    // Check for the minimal size
    if (pOpaqueData->GetSize() < 16)
    {
        HX_RELEASE(pOpaqueData);
        return HXR_FAIL;
    }

    // Pull out the width, height, and file size from the opaque data
    BYTE *pData = pOpaqueData->GetBuffer();
    BYTE  ucTmp;
    UnPack32(pData, m_ulImageWidth);
    UnPack32(pData, m_ulImageHeight);
    UnPack32(pData, m_ulTotalPackets);
    UnPack32(pData, m_ulDisplayTime);
    UnPack8(pData,  ucTmp);
    m_ulTarget  = ucTmp;

    // 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,  ucTmp);
//  UnPack32(pData, m_ulSeekTime);
    pData += 5;// Even though the 2 above members were removed we still need to increment the data pointer

    UINT16 usLen;
    UnPack16(pData, usLen);

    if (usLen)
    {
        HX_DELETE(m_pURL);
        m_pURL = new CHXString((const char*) pData, (int) usLen);
        if (!m_pURL)
        {
            HX_RELEASE(pOpaqueData);
            return HXR_FAIL;
        }
    }

    // Release our ref on the opaque data buffer
    HX_RELEASE(pOpaqueData);

    // Check for error in the opaque data
    if (!m_ulImageWidth || !m_ulImageHeight)
    {    
        return HXR_FAIL;
    }

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

    // Check the URL to see if it's relative
    if (m_pURL)
    {
        if (m_pURL->GetLength() > 0)
        {
            if (m_ulTarget == kTargetPlayer)
            {
                if (IsURLRelative((const char*) *m_pURL) &&
                    !strstr((const char*) *m_pURL, "command:"))
                {
                    CHXString cAbsURL;
                    retVal = MakeAbsoluteURL(cOriginalURL, *m_pURL, cAbsURL);
                    if (SUCCEEDED(retVal))
                    {
                        *m_pURL = cAbsURL;
                    }
                }
            }
        }
    }

    // Allocate a IJG library wrapper
    HX_DELETE(m_pDecomp);
    m_pDecomp = new CIJGLibraryWrapper();
    if (!m_pDecomp)
    {
        return HXR_OUTOFMEMORY;
    }

    // Initialize the library wrapper
    retVal = m_pDecomp->Initialize();
    if (FAILED(retVal))
    {
        HX_DELETE(m_pDecomp);
        return retVal;
    }

    // Get the transparency parameters from the stream header
    UINT32 ulDummy  = 0;
    BOOL   bDummy   = FALSE;
    BOOL   bMedOp   = m_bMediaOpacitySpecified;
    BOOL   bMedCK   = m_bMediaChromaKeySpecified;
    BOOL   bUseAlph = m_bUsesAlphaChannel;
    ParseTransparencyParameters(pHeader, ulDummy, bDummy,
                                m_ulMediaOpacity,
                                bMedOp,
                                m_ulMediaChromaKey,
                                bMedCK,
                                m_ulMediaChromaKeyTolerance,
                                m_ulMediaChromaKeyOpacity,
                                bUseAlph);
    m_bMediaOpacitySpecified   = bMedOp;
    m_bMediaChromaKeySpecified = bMedCK;
    m_bUsesAlphaChannel        = bUseAlph;
    // Set these transparency parameters into the decoding object
    if (m_bMediaOpacitySpecified)
    {
        m_pDecomp->SetOpacity(m_ulMediaOpacity);
    }
    if (m_bMediaChromaKeySpecified)
    {
        m_pDecomp->SetChromaKeyInfo(m_ulMediaChromaKey,
                                    m_ulMediaChromaKeyTolerance,
                                    m_ulMediaChromaKeyOpacity);
    }

    // Set the flag saying we're expecting the first packet
    m_bFirstPacket = TRUE;

    // Initialize the number of packets we've received
    m_ulNumPackets     = 0;
    m_bValidImage      = FALSE;
    m_bPastDisplayTime = FALSE;

    // Set the media opacity - CRNBaseRenderer (our grandparent
    // class) supports IHXValues
    SetPropertyULONG32("mediaOpacity", m_ulMediaOpacity);

    MLOG_MISC(m_pErrorMessages,
              "0x%08x:CJPEGRenderer::OnHeader()\n"
              "\tWidth=%lu\n"
              "\tHeight=%lu\n"
              "\tTotalPackets=%lu\n"
              "\tDisplayTime=%lu\n",
              this, m_ulImageWidth, m_ulImageHeight,
              m_ulTotalPackets, m_ulDisplayTime);

    return HXR_OK;
}


STDMETHODIMP CJPEGRenderer::OnPacketNoOffset(IHXPacket *pPacket)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x:CJPEGRenderer::OnPacketNoOffset() Packet %lu of %lu\n",
              this, m_ulNumPackets, m_ulTotalPackets);
    // Check for input error
    if (!pPacket)
    {
        return HXR_FAIL;
    }

    // Increment the number of packets
    m_ulNumPackets++;

    // If the packet is lost, we're done
    if (pPacket->IsLost())
    {
        if (m_ulNumPackets >= m_ulTotalPackets)
        {
            m_bValidImage = TRUE;
        }
        return HXR_OK;
    }

    // Get the buffer
    IHXBuffer *pBuffer = pPacket->GetBuffer();
    if (!pBuffer)
    {
        return HXR_FAIL;
    }

    // Is this the first packet
    HX_RESULT retVal;
    if (m_bFirstPacket)
    {
        // Clear the flag
        m_bFirstPacket = FALSE;

        // This packet should be a header packet
        BYTE *pData = pBuffer->GetBuffer() + 8;
        UINT32 ulSequenceNumber;
        UnPack32(pData, ulSequenceNumber);
        BOOL   bLost = (ulSequenceNumber == 0 ? FALSE : TRUE);
        if (bLost == TRUE)
        {
            // Set the wrapper flag to false - preventing use of any further packets
            m_pDecomp->SetValid(FALSE);
            HX_RELEASE(pBuffer);
            return HXR_OK;
        }

        // Initialize the last sequence number
        m_pDecomp->SetLastSequenceNumber(0);

        // Go ahead and add this buffer to the list
        m_pDecomp->AppendBuffer(pBuffer);

        // Release our reference on this buffer, since adding it to the list ref's it
        HX_RELEASE(pBuffer);

        // Set this flag so we won't add this packet's buffer twice
        m_pDecomp->SetNeedPacket(FALSE);

        // Create an IHXValues object
        IHXValues *pDecompValues = NULL;
        retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXValues, (void **) &pDecompValues);
        if (FAILED(retVal))
        {
            return retVal;
        }

        // Call the wrapper's ReadHeader() method
        retVal = m_pDecomp->ReadHeader(pDecompValues);
        if (FAILED(retVal))
        {
            HX_RELEASE(pDecompValues);
            return retVal;
        }

        // Extract the parameters
        UINT32 ulNumComponents;
        pDecompValues->GetPropertyULONG32("InputImageWidth",  m_ulImageWidth);
        pDecompValues->GetPropertyULONG32("InputImageHeight", m_ulImageHeight);
        pDecompValues->GetPropertyULONG32("NumComponents",    ulNumComponents);

        // Release our IHXValues object
        HX_RELEASE(pDecompValues);

        // Allocate a GImage
        HX_RELEASE(m_pImage);
        m_pImage = GImage::CreateObject();
        if (!m_pImage)
        {
            return HXR_OUTOFMEMORY;
        }
        m_pImage->AddRef();
        m_pImage->SetFactory(m_pCommonClassFactory);

        // Initialize the GImage
        UINT32 ulPadWidth  = m_ulImageWidth * 4;
        retVal = m_pImage->Init(m_ulImageWidth, m_ulImageHeight, 4, ulPadWidth);
        if (FAILED(retVal))
        {
            HX_RELEASE(m_pImage);
            return retVal;
        }

        // Set the output buffer
#ifdef _WINDOWS
        BOOL bRowsInverted = TRUE;
#else
        BOOL bRowsInverted = FALSE;
#endif
        retVal = m_pDecomp->SetOutputParameters(m_pImage->GetImageBuffer(),
                                                m_pImage->GetHeight() * m_pImage->GetRowStride(),
                                                m_pImage->GetRowStride(),
                                                bRowsInverted);
        if (FAILED(retVal))
        {
            HX_RELEASE(m_pImage);
            return retVal;
        }
    }
    else
    {
        if (m_pDecomp->GetValid() == FALSE)
        {
            HX_RELEASE(pBuffer);
            return HXR_OK;
        }

        /* If the packet is lost, then we need to insert data or surrender */
        BYTE     *pData = pBuffer->GetBuffer() + 8;
        UINT32    ulSequenceNumber;
        UnPack32(pData, ulSequenceNumber);
        BOOL      bLost = (ulSequenceNumber == m_pDecomp->GetLastSequenceNumber() + 1 ? FALSE : TRUE);
        HX_RESULT retVal;
        if (bLost == TRUE)
        {
            // Do we have restart markers in this image?
            if (m_pDecomp->GetRestartInterval() == 0)
            {
                // This image has no restart markers. Therefore, we set the image to invalid
                m_pDecomp->SetValid(FALSE);
                HX_RELEASE(pBuffer);
                return HXR_OK;
            } 

            // Insert some fake data with proper restart markers
            retVal = InsertDummyData(m_pDecomp, pBuffer);
            if (FAILED(retVal))
            {
                HX_RELEASE(pBuffer);
                return retVal;
            }
        }

        // Update the last sequence number
        m_pDecomp->SetLastSequenceNumber(ulSequenceNumber);

        // Append this buffer to the wrapper's list
        m_pDecomp->AppendBuffer(pBuffer);

        // Release our reference on the buffer
        HX_RELEASE(pBuffer);

        // Now call decompress for the wrapper
        retVal = m_pDecomp->Decompress();
        if (FAILED(retVal))
        {
            m_pDecomp->SetValid(FALSE);
        }
    }

    // If we've received all the packets we're going to receive, then we
    // set the flag saying we've got an image to display
    if (m_ulNumPackets >= m_ulTotalPackets)
    {
        MLOG_MISC(m_pErrorMessages,
                  "0x%08x:CJPEGRenderer::OnPacketNoOffset()\n"
                  "\tgot all packets - setting valid to TRUE\n", this);
        // If we got one or more of our transparency parameters
        // set after we starting decoding, then a flag was set
        // in order to make us do an AdjustTransparency() after
        // we finished decoding
        if (m_bPendingTransparencyBlt)
        {
            UINT32 ulNumPix = m_pImage->GetWidth() * m_pImage->GetHeight();
            BOOL bUseAlpha = m_bUsesAlphaChannel;
            AdjustTransparency(m_pImage->GetImageBuffer(),
                               ulNumPix,
                               m_bMediaOpacitySpecified,
                               m_ulMediaOpacity,
                               m_bMediaChromaKeySpecified,
                               m_ulMediaChromaKey,
                               m_ulMediaChromaKeyTolerance,
                               m_ulMediaChromaKeyOpacity,
                               bUseAlpha);
            m_bUsesAlphaChannel = bUseAlpha;
            // Clear the flag
            m_bPendingTransparencyBlt = FALSE;
        }
        // Now set the valid image flag
        m_bValidImage = TRUE;
    }

    // If we are still getting packets past our display time,
    // then force a redraw after receiving a packet
    if (m_bPastDisplayTime && m_pSite)
    {
        // Redraw our data by damaging the entire area of our data
        HXxSize size = {0, 0};
        m_pSite->GetSize(size);
        HXxRect damageRect = {0, 0, size.cx, size.cy};
        m_pSite->DamageRect(damageRect);
        m_pSite->ForceRedraw();
    }

    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::OnTimeSyncOffset(UINT32 ulTime)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x:CJPEGRenderer::OnTimeSyncOffset(%lu)\n",
              this, ulTime);
    // Are we supposed to draw the image?
    if (ulTime >= m_ulDisplayTime && !m_bPastDisplayTime)
    {
        MLOG_MISC(m_pErrorMessages,
                  "\tforcing redraw in OnTimeSyncOffset(%lu)\n",
                  ulTime);
        m_bPastDisplayTime = TRUE;
        // Redraw our data by damaging the entire area of our data
        HXxSize size;
        m_pSite->GetSize(size);
        HXxRect damageRect = {0, 0, size.cx, size.cy };
        m_pSite->DamageRect(damageRect);
        m_pSite->ForceRedraw();
    }

    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::GetWindowSize(REF(HXxSize) rSize)
{
    rSize.cx = m_ulImageWidth;
    rSize.cy = m_ulImageHeight;
    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::IsMouseOverActiveLink(INT16 x, INT16 y, REF(BOOL) rbActive, REF(IHXBuffer*) rpLink)
{
    HX_RESULT retVal = HXR_OK;

    // By default we are NOT over a link
    rbActive = FALSE;

    // Make sure mouse is in the window
    if (x >= 0 && x < (INT16) m_ulImageWidth &&
        y >= 0 && y < (INT16) m_ulImageHeight)
    {
        // Make sure a URL string exists
        if (m_pURL)
        {
            // Make sure the URL string has non-zero length
            if (m_pURL->GetLength() > 0)
            {
                // Create an IHXBuffer object
                IHXBuffer* pLinkStr = NULL;
                HX_RESULT   retVal   = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
                                                                             (void**) &pLinkStr);
                if (SUCCEEDED(retVal))
                {
                    // Set the buffer's value
                    retVal = pLinkStr->Set((const BYTE*) m_pURL->GetBuffer(0), m_pURL->GetLength() + 1);
                    if (SUCCEEDED(retVal))
                    {
                        rbActive = TRUE;
                        rpLink   = pLinkStr;
                    }
                }
            }
        }
    }

    return retVal;
}

STDMETHODIMP CJPEGRenderer::RMASurfaceUpdate(IHXVideoSurface* pSurface)
{
    MLOG_MISC(m_pErrorMessages,
              "0x%08x:CJPEGRenderer::RMASurfaceUpdate()\n"
              "\tm_pImage=0x%08x\n"
              "\tm_bValidImage=%lu\n"
              "\tm_bPastDisplayTime=%lu\n",
              this, m_pImage, m_bValidImage, m_bPastDisplayTime);
    if (pSurface)
    {
	if (m_pImage && m_pImage->GetImageBuffer() && m_bPastDisplayTime)
	{
	    HXxSize size;
	    m_pSite->GetSize(size);
	    DrawToRMASurface(pSurface, 0, 0, size);
	}
	else if (m_pSite)
	{
	    pSurface->AddRef();

	    // Get the current size of the site
	    HXxSize size;
	    m_pSite->GetSize(size);

	    // The source rect is just a 1 by 1 black pixel that gets stretched to fit the destination
	    HXxRect rDestRect   = { 0, 0, size.cx, size.cy};
	    HXxRect rSrcRect    = { 0, 0, 1, 1};
	
	    HXBitmapInfoHeader bih;

	    bih.biSize          = sizeof(HXBitmapInfoHeader);
	    bih.biWidth         = 1;
	    bih.biHeight        = 1;
	    bih.biPlanes        = 1;
	    bih.biBitCount      = 32;
            bih.biCompression   = HX_RGB;
	    bih.biSizeImage     = 0;
	    bih.biXPelsPerMeter = 0;
	    bih.biYPelsPerMeter = 0;
	    bih.biClrUsed       = 0;
	    bih.biClrImportant  = 0;

	    // Using 32 bit color so there is no color table just an array of RGBQUAD values.  So just set the
	    // 1 bit in our bitmap to black.
	    unsigned char pBuffer[4] = { 0,0,0,0 };
	    pSurface->Blt(pBuffer,
			       &bih,
			       rDestRect,
			       rSrcRect);

	    pSurface->Release();
	}
    }

    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::HandleClick(INT16 x, INT16 y)
{
    if (m_pHyperNavigate && m_pURL && m_pURL->GetLength() > 0)
    {
	m_pHyperNavigate->GoToURL(*m_pURL, (m_ulTarget == kTargetPlayer) ? "_player" : (const char*) NULL);
    }

    return HXR_OK;
}

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

    if (pName)
    {
        // Check to see if this is a property we're interested
        // in monitoring
        if (!strcmp(pName, "mediaOpacity"))
        {
            m_ulMediaOpacity         = ulVal;
            m_bMediaOpacitySpecified = TRUE;
        }
        else if (!strcmp(pName, "chromaKey"))
        {
            m_ulMediaChromaKey         = ulVal;
            m_bMediaChromaKeySpecified = TRUE;
        }
        else if (!strcmp(pName, "chromaKeyTolerance"))
        {
            m_ulMediaChromaKeyTolerance = ulVal;
        }
        else if (!strcmp(pName, "chromaKeyOpacity"))
        {
            m_ulMediaChromaKeyOpacity = ulVal;
        }
        // Have we received the first packet yet?
        if (m_bFirstPacket)
        {
            // If we haven't yet received the first packet, then
            // we can set the parameters into the decoding object
            // and the decoding will automatically include these
            // parameters when it decodes. However, if the decoding
            // has already started (i.e. - m_bFirstPacket == FALSE),
            // then we will have to make a pass through our buffer
            // at the end to force the opacity and enforce the
            // chroma key.
            if (m_bMediaOpacitySpecified)
            {
                m_pDecomp->SetOpacity(m_ulMediaOpacity);
                if (m_ulMediaOpacity < 255)
                {
                    m_bUsesAlphaChannel = TRUE;
                }
            }
            if (m_bMediaChromaKeySpecified)
            {
                m_pDecomp->SetChromaKeyInfo(m_ulMediaChromaKey,
                                            m_ulMediaChromaKeyTolerance,
                                            m_ulMediaChromaKeyOpacity);
                m_bUsesAlphaChannel = TRUE;
            }
        }
        else
        {
            // Have we finished decoding all the packets?
            if (m_bValidImage)
            {
                // We have already decoded all the packets, so we can
                // simply make a pass through the decoded buffer with the new
                // transparency parameters.
                UINT32 ulNumPix = m_pImage->GetWidth() * m_pImage->GetHeight();
                BOOL bUseAlph   = m_bUsesAlphaChannel;
                AdjustTransparency(m_pImage->GetImageBuffer(),
                                   ulNumPix,
                                   m_bMediaOpacitySpecified,
                                   m_ulMediaOpacity,
                                   m_bMediaChromaKeySpecified,
                                   m_ulMediaChromaKey,
                                   m_ulMediaChromaKeyTolerance,
                                   m_ulMediaChromaKeyOpacity,
                                   bUseAlph);
                m_bUsesAlphaChannel = bUseAlph;
            }
            else
            {
                // We have processed some packets, but we're not
                // done yet. Therefore, we must set a pending flag
                // which will trigger a transparency pass through the buffer
                // once the decoding is done.
                m_bPendingTransparencyBlt = TRUE;
            }
        }
        // Now pass it off to our grandparent class
        retVal = CRNBaseRenderer::SetPropertyULONG32(pName, ulVal);
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

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

    if (ppIUnknown)
    {
        // Set default
        *ppIUnknown = NULL;
        // Create the object
        CJPEGRenderer *pObj = new CJPEGRenderer();
        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;
}

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

    // Make sure we have valid argument and valid
    // buffer to display
    if (pSurface && pDirtyRegion  && m_pImage &&
        m_pImage->GetImageBuffer() &&
        m_bPastDisplayTime && m_pSite)
    {
        // Get the size of the site
        HXxSize size;
        m_pSite->GetSize(size);
        // Scale dirty rects to current display size
        float fx = 1.0;
        float fy = 1.0;
        if (size.cx > 0 && size.cy > 0)
        {
            fx = (float) m_pImage->GetWidth()  / (float) size.cx;
            fy = (float) m_pImage->GetHeight() / (float) size.cy;
        }
        // Go through each rect in the dirty region and scale it to 
        // generate the src rects.
        if (pDirtyRegion->numRects > 0)
        {
            // Create an array to hold the scaled subrects
            HXBOX* pSrcRects = new HXBOX[pDirtyRegion->numRects];
            for(INT32 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 up the bitmap info header
            HXBitmapInfoHeader cHeader;
            cHeader.biSize          = 40;
            cHeader.biWidth         = (INT32) m_pImage->GetWidth();
            cHeader.biHeight        = (INT32) m_pImage->GetHeight();
            cHeader.biPlanes        = 1;
            cHeader.biBitCount      = 32;
            cHeader.biCompression   = (m_bUsesAlphaChannel ? HX_ARGB : 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;
            // Get the image store
            BYTE* pBits = m_pImage->GetImageBuffer();
            if (pBits)
            {
                MLOG_MISC(m_pErrorMessages,
                          "\t\tnumRects=%ld\n",
                          srcRegion.numRects);
                for (INT32 i = 0; i < srcRegion.numRects; i++)
                {
                    MLOG_MISC(m_pErrorMessages,
                              "\t\t\trect[%ld] = (%d,%d,%d,%d)\n",
                              i,
                              srcRegion.rects[i].x1,
                              srcRegion.rects[i].y1,
                              srcRegion.rects[i].x2,
                              srcRegion.rects[i].y2);
                }
                // Blit to the video surface
                pSurface->BltSubRects(pBits,
                                      &cHeader,
                                      pDirtyRegion,
                                      &srcRegion,
                                      1.0/fx, 1.0/fy);
            }
            // Delete the array of scaled subrects
            HX_VECTOR_DELETE(pSrcRects);
        }
    }
    else
    {
        retVal = HXR_FAIL;
    }

    return retVal;
}

void CJPEGRenderer::_AttachSite()
{
    if (m_pSite)
    {
        // Sign up to receive sub rect messages
        IHXSubRectSite* pSubRectSite = NULL;
        m_pSite->QueryInterface(IID_IHXSubRectSite, (void**) &pSubRectSite);
        if(pSubRectSite)
        {
            // If so, since IHXSubRectSite inherits from IHXSite, lets
            // just swap the pointers and sign up for the service.
            HX_RELEASE(m_pSite);
            m_pSite = pSubRectSite;
            pSubRectSite->SendSubRectMessages(TRUE);
        }
    }
}

HX_RESULT CJPEGRenderer::InsertDummyData(CIJGLibraryWrapper *pWrap, IHXBuffer *pCurrentBuffer)
{
    // Get the last buffer appended to the library's decompression buffer list
    IHXBuffer *pLastBuffer = NULL;
    pWrap->GetLastPacketBuffer(&pLastBuffer);
    if (pLastBuffer == NULL)
    {
        pWrap->SetValid(FALSE);
        return HXR_UNEXPECTED;
    }

    // Compute the number of lost packets
    BYTE   *pCurBuf = pCurrentBuffer->GetBuffer() + 8;
    BYTE   *pLstBuf = pLastBuffer->GetBuffer() + 8;
    UINT32 ulCurrSeqNum;
    UnPack32(pCurBuf, ulCurrSeqNum);
    UINT32 ulLastSeqNum;
    UnPack32(pLstBuf, ulLastSeqNum);
    UINT32 ulNumLostPackets = ulCurrSeqNum - ulLastSeqNum - 1;

    // This image has restart markers. So we need to first find out the
    // value of the last restart marker. We know, because we parsed the image,
    // that the last two bytes of a packet will be 0xFF 0xDq where q = [0,7].
    // If the last two bytes are NOT that, then this is the first data packet.
    BYTE   *pData            = pLastBuffer->GetBuffer() + 16;
    UINT32  ulLen            = pLastBuffer->GetSize();
    UINT32  ulLastByte       = pData[ulLen - 1];
    UINT16  usTmp;
    UnPack16(pData, usTmp);
    UINT32  ulLastStartBlock = usTmp;
    UnPack16(pData, usTmp);
    UINT32  ulLastBlockCount = usTmp;

    // Compute the next marker
    UINT32 ulNextMarker;
    if (ulLastSeqNum > 0)
    {
        // The last packet received was not the header packet. Therefore, we
        // should be able to look at the last two bytes of the last packet
        ulNextMarker = ulLastByte + 1;
        if (ulNextMarker == 0xD8)
        {
            ulNextMarker = 0xD0;
        }
    }
    else
    {
        // The last packet received was the header packet. Therefore,
        // the first restart marker is 0xD0
        ulNextMarker = 0xD0;
    }

    // Get the current starting block index
    pData = pCurrentBuffer->GetBuffer() + 16;
    UnPack16(pData, usTmp);
    UINT32 ulCurStartBlock = usTmp;

    // Compute the number of blocks to fill in and the size of the dummy buffer
    UINT32 ulNumBlocksToFill = ulCurStartBlock - ulLastStartBlock - ulLastBlockCount;
    if (ulNumBlocksToFill < ulNumLostPackets)
    {
        return HXR_FAILED;
    }

    // What we will do is put 1 block per lost packet up until the last lost packet,
    // where we will fill in the rest of the lost blocks
    for (UINT32 i = 0; i < ulNumLostPackets; i++)
    {
        UINT32 ulNumBlocksInThisPacket = 0;
        if (i == ulNumLostPackets - 1)
        {
            // We're doing the last lost packet, so we fill
            // in the rest of the lost blocks in this packet
            ulNumBlocksInThisPacket = ulNumBlocksToFill - (ulNumLostPackets - 1);
        }
        else
        {
            // We fill in only 1 block in this packet
            ulNumBlocksInThisPacket = 1;
        }

        // Compute the size of the opaque data of this packet
        UINT32 ulDummySize = kJPEGPacketOverhead + ulNumBlocksInThisPacket * 6;

        // Create an IHXBuffer
        IHXBuffer *pDummy = NULL;
        HX_RESULT retVal = m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer,
                                                                 (void **) &pDummy);
        if (retVal != HXR_OK || pDummy == NULL)
        {
            return HXR_FAILED;
        }

        // Set the size
        retVal = pDummy->SetSize(kJPEGPacketOverhead + ulNumBlocksInThisPacket * 6);
        if (retVal != HXR_OK)
        {
            return retVal;
        }

        // Loop through the blocks to fill in dummy data
        pData = pDummy->GetBuffer() + kJPEGPacketOverhead;
        for (UINT32 j = ulNumBlocksInThisPacket; j; j--)
        {
            // Set this block
            pData[0] = (BYTE) 0;
            pData[1] = (BYTE) 0;
            pData[2] = (BYTE) 0;
            pData[3] = (BYTE) 0;
            pData[4] = (BYTE) 0xFF;
            pData[5] = (BYTE) ulNextMarker;

            // Increment the buffer pointer
            pData   += 6;

            // Modulo-8 increment the marker
            ulNextMarker++;
            if (ulNextMarker == 0xD8)
            {
                ulNextMarker = 0xD0;
            }
        }

        // Now append this to the buffer list
        pWrap->AppendBuffer(pDummy);

        // Now we can release the buffer, since the wrapper AddRef's it
        HX_RELEASE(pDummy);
    }

    return HXR_OK;
}

void CJPEGRenderer::DrawToRMASurface(IHXVideoSurface *pVideoSurface, UINT32 ulX, UINT32 ulY, const HXxSize &size)
{
    if (m_pImage)
    {
        HXxRect rDestRect = { 0, 0, size.cx, size.cy};
        HXxRect rSrcRect  = { 0, 0, m_pImage->GetWidth(),
                                    m_pImage->GetHeight() };

        HXBitmapInfoHeader cHeader;

        cHeader.biSize          = 40;
        cHeader.biWidth         = (INT32) m_pImage->GetWidth();
        cHeader.biHeight        = (INT32) m_pImage->GetHeight();
        cHeader.biPlanes        = 1;
        cHeader.biBitCount      = 32;
        cHeader.biCompression   = (m_bUsesAlphaChannel ? HX_ARGB : HX_RGB);
        cHeader.biSizeImage     = 0;
        cHeader.biXPelsPerMeter = 0;
        cHeader.biYPelsPerMeter = 0;
        cHeader.biClrUsed       = 0;
        cHeader.biClrImportant  = 0;

        MLOG_MISC(m_pErrorMessages,
                  "\tDrawToRMASurface()\n"
                  "\t\tsrc: (%ld,%ld,%ld,%ld)\n"
                  "\t\tdst: (%ld,%ld,%ld,%ld)\n",
                  rSrcRect.left,  rSrcRect.top,  rSrcRect.right,  rSrcRect.bottom,
                  rDestRect.left, rDestRect.top, rDestRect.right, rDestRect.bottom);
        pVideoSurface->Blt(m_pImage->GetImageBuffer(),
                           &cHeader,
                           rDestRect,
                           rSrcRect);
    }
}

void CJPEGRenderer::AdjustTransparency(BYTE*     pBuffer,
                                       UINT32    ulNumPix,
                                       BOOL      bMediaOpacitySpecified,
                                       UINT32    ulMediaOpacity,
                                       BOOL      bMediaChromaKeySpecified,
                                       UINT32    ulMediaChromaKey,
                                       UINT32    ulMediaChromaKeyTolerance,
                                       UINT32    ulMediaChromaKeyOpacity,
                                       REF(BOOL) rbUsesAlphaChannel)
{
    // Make sure have some work to do
    if (pBuffer && ulNumPix &&
        (bMediaOpacitySpecified ||
         bMediaChromaKeySpecified))
    {
        // Clip the opacity values
        if (ulMediaOpacity > 255) ulMediaOpacity = 255;
        if (ulMediaChromaKeyOpacity > 255) ulMediaChromaKeyOpacity = 255;
        // Get a 32bit pointer
        UINT32* pPix = (UINT32*) pBuffer;
        // Check to see what we need to do. We will
        // break these out, since they will optimize
        // best if there is not unused code in each loop
        if (bMediaOpacitySpecified &&
            !bMediaChromaKeySpecified)
        {
            // If we are forcing any other opacity less
            // than 255, then we KNOW we will be using 
            // the alpha channel
            rbUsesAlphaChannel  = (ulMediaOpacity < 255 ? TRUE : FALSE);
            // Compute the media alpha
            UINT32 ulMediaAlpha = (255 - ulMediaOpacity) << 24;
            // Run through the pixels, forcing this alpha
            while (ulNumPix--)
            {
                *pPix++ = (*pPix & 0x00FFFFFF) | ulMediaAlpha;
            }
        }
        else if (!bMediaOpacitySpecified &&
                 bMediaChromaKeySpecified)
        {
            // If the chroma key opacity is less than 255, then
            // we MAY be using the alpha channel, depending on
            // whether there are any pixels which match the chroma key
            BOOL   bNeedAlpha = (ulMediaChromaKeyOpacity < 255 ? TRUE : FALSE);
            // Initially set the flag to FALSE
            rbUsesAlphaChannel = FALSE;
            // Compute the chroma key alpha
            UINT32 ulChromaAlpha = (255 - ulMediaChromaKeyOpacity) << 24;
            // Run through the pixels
            while (ulNumPix--)
            {
                if (DoesChromaKeyMatch(*pPix, ulMediaChromaKey, ulMediaChromaKeyTolerance))
                {
                    *pPix = (*pPix & 0x00FFFFFF) | ulChromaAlpha;
                    rbUsesAlphaChannel = bNeedAlpha;
                }
                pPix++;
            }
        }
        else if (bMediaOpacitySpecified &&
                 bMediaChromaKeySpecified)
        {
            // If we are forcing any other opacity less
            // than 255, then we KNOW we will be using 
            // the alpha channel
            rbUsesAlphaChannel  = (ulMediaOpacity < 255 ? TRUE : FALSE);
            // If the chroma key opacity is less than 255, then
            // we MAY be using the alpha channel, depending on
            // whether there are any pixels which match the chroma key
            BOOL bNeedAlpha = (ulMediaOpacity < 255 ||
                               ulMediaChromaKeyOpacity < 255 ? TRUE : FALSE);
            // If the pixel is a chroma key match, then we
            // will be replacing it with the chroma key opacity
            // SCALED by the media opacity. If not, then we simply
            // force the media opacity
            UINT32 ulScaledChromaOpacity = ulMediaChromaKeyOpacity * ulMediaOpacity / 255;
            UINT32 ulChromaAlpha         = (255 - ulScaledChromaOpacity) << 24;
            UINT32 ulMediaAlpha          = (255 - ulMediaOpacity) << 24;
            // Run through the pixels
            while (ulNumPix--)
            {
                // Check the chroma key
                if (DoesChromaKeyMatch(*pPix, ulMediaChromaKey, ulMediaChromaKeyTolerance))
                {
                    *pPix++ = (*pPix & 0x00FFFFFF) | ulChromaAlpha;
                    rbUsesAlphaChannel = bNeedAlpha;
                }
                else
                {
                    *pPix++ = (*pPix & 0x00FFFFFF) | ulMediaAlpha;
                }
            }
        }
    }
}

STDMETHODIMP CJPEGRenderer::GetName(REF(const char*) rpszName)
{
    rpszName = (const char*) m_pszName;
    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::GetDescription(REF(const char*) rpszDescription)
{
    rpszDescription = (const char*) m_pszDescription;
    return HXR_OK;
}

STDMETHODIMP CJPEGRenderer::GetMimeTypes(REF(const char**) rppszMimeType)
{
    rppszMimeType = (const char**) m_ppszMimeType;
    return HXR_OK;
}

STDMETHODIMP_(UINT32) CJPEGRenderer::GetPluginVersion()
{
    return TARVER_ULONG32_VERSION;
}

STDMETHODIMP_(UINT32) CJPEGRenderer::GetInitialGranularity()
{
    return 100;
}

STDMETHODIMP_(UINT32) CJPEGRenderer::GetHighestSupportedStreamVersion()
{
    return HX_ENCODE_PROD_VERSION(0, 1, 0, 0);
}

