//------------------------------------------------------------------------------
// File: Bitmap.cpp
//
// Desc: DirectShow sample code - Bitmap manipulation routines for 
//       VMR alpha-blended bitmap
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include <dshow.h>
#include <tchar.h>
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>
#include <atlbase.h>

#include "watermark.h"
#include "bitmap.h"

//
// Constants
//
const float EDGE_BUFFER  = 0.04f;  // Pixel buffer between bitmap and window edge
                                   // (represented in composition space [0 - 1.0f])

const float SLIDE_VALUE  = 0.05f;  // Amount to slide image in composition space
const float STROBE_VALUE = 0.125f; // Amount to add to bitmap alpha value

const int ANIMATE_TIMER  = 2000;   // Timer IDs
const int SLIDE_TIMER    = 2001;
const int STROBE_TIMER   = 2002;
const int MAIN_TIMER     = 2003;

const int ANIMATE_TIMEOUT = 250;   // Timer delays in milliseconds
const int SLIDE_TIMEOUT   = 125;
const int STROBE_TIMEOUT  = 125;
const int MAIN_TIMEOUT    = 125;

const int NUM_IMAGES_IN_BITMAP = 5; // Five images in single wide bitmap (320 x 64)

//
// Global data
//
IVMRMixerBitmap *pBMP = NULL;
DWORD g_dwWatermarkFlags=0;
int gnTimer=0;

float g_fBitmapCompWidth=0;
int g_nBitmapWidth=0;               // Width of wide multi-image bitmap
int g_nImageWidth=0;                // Width of single image in wide multi-image bitmap
BOOL g_bRestoreFlip=0, g_bRestoreMirror=0;
HBITMAP g_hbmAnimate=0;
BOOL g_bWatermarkDisabled=FALSE;

// Rectangles used for alpha-blended watermark
RECT            g_rSrc={0}, g_rSrcSingle={0};
NORMALIZEDRECT  g_rDest={0};



HRESULT BlendApplicationImage(HWND hwndApp)
{
    LONG cx, cy;
    HRESULT hr;
    RECT rc={0};

    // Read the default video size
    hr = pWC->GetNativeVideoSize(&cx, &cy, NULL, NULL);
    if (FAILED(hr))
      return hr;

    // Load the multi-image bitmap to alpha blend from the resource file
    HBITMAP hbm = LoadBitmap(ghInst, MAKEINTRESOURCE(IDR_VMR_WIDE));

    BITMAP bm;
    HBITMAP hbmOld;
    HDC hdc = GetDC(hwndApp);
    HDC hdcBmp = CreateCompatibleDC(hdc);
    ReleaseDC(hwndApp, hdc);

    GetObject(hbm, sizeof(bm), &bm);
    hbmOld = (HBITMAP)SelectObject(hdcBmp, hbm);

    // Configure the VMR's bitmap structure
    VMRALPHABITMAP bmpInfo;
    ZeroMemory(&bmpInfo, sizeof(bmpInfo) );
    bmpInfo.dwFlags = VMRBITMAP_HDC;
    bmpInfo.hdc = hdcBmp;

    // The wide bitmap contains five similar images that can be
    // cycled to provide the illusion of animation.
    g_nBitmapWidth = bm.bmWidth;
    g_nImageWidth  = bm.bmWidth / NUM_IMAGES_IN_BITMAP;

    // Display the bitmap in the bottom right corner.
    // rSrc specifies the source rectangle in the GDI device context.
    // To enable animating between multiple single images within a wide 
    // bitmap, we must specify the entire rectangle of the wide image.
    // The VMR will convert this rectangle into a DirectDraw surface,
    // within which we can select a smaller source rectangle to display.
    SetRect(&rc, 0, 0, g_nBitmapWidth, bm.bmHeight);
    bmpInfo.rSrc = rc;

    // rDest specifies the destination rectangle in composition space (0.0f to 1.0f)
    bmpInfo.rDest.left   = (float)(cx - g_nImageWidth) / (float)cx - EDGE_BUFFER;
    bmpInfo.rDest.top    = (float)(cy - bm.bmHeight)   / (float)cy - EDGE_BUFFER;
    bmpInfo.rDest.right  = 1.0f - EDGE_BUFFER;
    bmpInfo.rDest.bottom = 1.0f - EDGE_BUFFER;

    // Copy initial settings to global memory for later modification
    g_rDest = bmpInfo.rDest;
    g_rSrc  = bmpInfo.rSrc;

    // Save the ratio of the bitmap's width to the width of the video file.
    // This value is used to reposition the bitmap in composition space.
    g_fBitmapCompWidth = (float)g_nImageWidth / (float)cx;

    // Transparency value 1.0 is opaque, 0.0 is transparent.
    // For initially setting the bitmap, we'll make it transparent
    // because we need to give the VMR the entire wide image.
    bmpInfo.fAlpha = 0.0;

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    // Give the bitmap to the VMR.  Since the alpha value is 0, nothing will
    // be displayed yet on the screen, but the VMR will have the information
    // that it needs to allow us to modify the bitmap.
    hr = pBMP->SetAlphaBitmap(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("SetAlphaBitmap FAILED!  Bitmap operations will fail. hr=0x%x\r\n"), hr);

    // Clean up GDI resources
    DeleteObject(SelectObject(hdcBmp, hbmOld));
    DeleteObject(hbm);
    DeleteDC(hdcBmp);

    // If setting the alpha bitmap succeeded, update its parameters
    if (SUCCEEDED(hr))
    {
        //
        // Now change the size of the source rectangle to a single
        // image width.  Update the alpha so that the image will be
        // properly displayed.
        //
        SetRect(&rc, 0, 0, g_nImageWidth, bm.bmHeight);
        bmpInfo.rSrc = rc;

        // Save the single-image rectangle for later reference during animation
        g_rSrcSingle = rc;

        // Set the necessary flags to update the source rectangle
        bmpInfo.dwFlags = VMRBITMAP_SRCRECT | VMRBITMAP_SRCCOLORKEY;

        // Transparency value 1.0 is opaque, 0.0 is transparent.
        bmpInfo.fAlpha = TRANSPARENCY_VALUE;

        // Update the source rectangle and alpha values of the bitmap.
        // Now the image will appear properly on the screen.
        hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
        if (FAILED(hr))
            Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);
    }

    return hr;
}


void ClearWatermarkState(void)
{
    KillTimer();

    // Clear current watermark flags
    g_dwWatermarkFlags = 0;

    // Enable all watermark menu items, which may have been
    // disabled if the watermark was currently disabled
    EnableWatermarkMenu(TRUE);

    // Clear menu check marks
    CheckMenuItem(ghMenu, ID_FLIP,    MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_MIRROR,  MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_ANIMATE, MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_SLIDE,   MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_STROBE,  MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_ALL_EFFECTS, MF_UNCHECKED);
    CheckMenuItem(ghMenu, ID_NO_EFFECTS,  MF_UNCHECKED);
}


void EnableWatermarkMenu(BOOL bEnable)
{
    EnableMenuItem(ghMenu, ID_FLIP,    bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_MIRROR,  bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_ANIMATE, bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_SLIDE,   bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_STROBE,  bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_ALL_EFFECTS, bEnable ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(ghMenu, ID_NO_EFFECTS,  bEnable ? MF_ENABLED : MF_GRAYED);

    g_bWatermarkDisabled = !bEnable;
}


HRESULT MirrorWatermark(DWORD dwFlags)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Configure the X axis
    if (dwFlags & MARK_MIRROR)
    {
        // Swap left/right coordinates
        float fLeft = bmpInfo.rDest.left;
        bmpInfo.rDest.left = bmpInfo.rDest.right;
        bmpInfo.rDest.right = fLeft;

        CheckMenuItem(ghMenu, ID_MIRROR, MF_CHECKED);
    }
    else
    {
        // If we're removing the mirror effect while sliding, then
        // it's not correct to reset the left/right coordinates to
        // the default saved global values.  Instead, just switch 
        // the left/right values.
        if (dwFlags & MARK_SLIDE)
        {
            // Swap left/right coordinates
            float fLeft = bmpInfo.rDest.left;
            bmpInfo.rDest.left = bmpInfo.rDest.right;
            bmpInfo.rDest.right = fLeft;
        }
        else
        {
            // We're not sliding, so just reset the left/right
            // coordinates to their original default values.
            bmpInfo.rDest.left  = g_rDest.left;
            bmpInfo.rDest.right = g_rDest.right;
        }

        CheckMenuItem(ghMenu, ID_MIRROR, MF_UNCHECKED);
    }

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);

    return hr;
}


HRESULT FlipWatermark(DWORD dwFlags)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Configure the Y axis
    if (dwFlags & MARK_FLIP)
    {
        // Swap left/right coordinates
        float fTop = bmpInfo.rDest.top;
        bmpInfo.rDest.top = bmpInfo.rDest.bottom;
        bmpInfo.rDest.bottom = fTop;

        CheckMenuItem(ghMenu, ID_FLIP, MF_CHECKED);
    }
    else
    {
        bmpInfo.rDest.top = g_rDest.top;
        bmpInfo.rDest.bottom = g_rDest.bottom;

        CheckMenuItem(ghMenu, ID_FLIP, MF_UNCHECKED);
    }

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);

    return hr;
}


HRESULT DisableWatermark(DWORD dwFlags)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    // Since the enable/disable menu will always be enabled,
    // verify that the VMR Alpha bitmap interface is set.
    if (!pBMP)
        return E_NOINTERFACE;

    // Read the current bitmap settings
    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // If the request is to disable the bitmap, then disable it
    // and ignore the other flags for now
    if (dwFlags & MARK_DISABLE)
    {
        // Temporarily disable bitmap display
        bmpInfo.dwFlags = VMRBITMAP_DISABLE;

        // Disable other watermark menu items
        EnableWatermarkMenu(FALSE);

        // Remember if the flip/mirror states are set for redrawing
        if (g_dwWatermarkFlags & MARK_FLIP)
            g_bRestoreFlip = TRUE;
        if (g_dwWatermarkFlags & MARK_MIRROR)
            g_bRestoreMirror = TRUE;

        // Update the bitmap settings
        hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
        if (FAILED(hr))
            Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);
    }
    else
    {
        // Reset the bitmap with default values
        hr = BlendApplicationImage(ghApp);

        // Reenable other watermark menu items
        EnableWatermarkMenu(TRUE);

        // Preserve the previously-set mirror/flip settings
        if (g_bRestoreFlip)
        {
            FlipWatermark(g_dwWatermarkFlags);
            g_bRestoreFlip = FALSE;
        }
        if (g_bRestoreMirror)
        {
            MirrorWatermark(g_dwWatermarkFlags);
            g_bRestoreMirror = FALSE;
        }
    }

    return hr;
}


void FlipFlag(DWORD dwFlag)
{
    // If the flag is set, clear it
    if (g_dwWatermarkFlags & dwFlag)
        g_dwWatermarkFlags &= ~dwFlag;

    // Otherwise, set the flag
    else
        g_dwWatermarkFlags |= dwFlag;
}


void AnimateWatermark(DWORD dwFlags)
{
    if (dwFlags & MARK_ANIMATE)
    {
        StartTimer();
        CheckMenuItem(ghMenu, ID_ANIMATE, MF_CHECKED);
    }
    else
    {
        CheckMenuItem(ghMenu, ID_ANIMATE, MF_UNCHECKED);
        ResetAnimation();
    }
}


void SlideWatermark(DWORD dwFlags)
{
    if (dwFlags & MARK_SLIDE)
    {
        StartTimer();
        CheckMenuItem(ghMenu, ID_SLIDE, MF_CHECKED);
    }
    else
    {
        CheckMenuItem(ghMenu, ID_SLIDE, MF_UNCHECKED);
        ResetBitmapPosition();
    }
}


void StrobeWatermark(DWORD dwFlags)
{
    // Start cycling the bitmap's alpha value on a timer
    if (dwFlags & MARK_STROBE)
    {
        StartTimer();
        CheckMenuItem(ghMenu, ID_STROBE, MF_CHECKED);
    }
    else
    {
        CheckMenuItem(ghMenu, ID_STROBE, MF_UNCHECKED);
        ResetBitmapAlpha();
    }
}


void StartTimer(void)
{
    if (!gnTimer)
        gnTimer = (int) SetTimer(NULL, MAIN_TIMER, MAIN_TIMEOUT, TimerProc);
}

void KillTimer(void)
{
    if (gnTimer)
    {
        KillTimer(NULL, gnTimer);
        gnTimer = 0;
    }
}


VOID CALLBACK TimerProc(
  HWND hwnd,         // handle to window
  UINT uMsg,         // WM_TIMER message
  UINT_PTR idEvent,  // timer identifier
  DWORD dwTime       // current system time
)
{
    static int nToggle=0;

    // If the user has disabled the watermark, just exit.
    if (g_bWatermarkDisabled)
        return;

    // Use one timer for efficiency, instead of starting/stopping
    // three separate timers.  Since animation will run at half the
    // rate of slide/strobe, only process animation in every other callback.
    if (g_dwWatermarkFlags & MARK_SLIDE)
        HandleSlide();

    if (g_dwWatermarkFlags & MARK_STROBE)
        HandleStrobe();

    if (g_dwWatermarkFlags & MARK_ANIMATE)
    {
        nToggle ^= 1;
        if (nToggle)
            HandleAnimation();
    }
}


void HandleStrobe(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Slowly increase the alpha value
    float fAlpha = bmpInfo.fAlpha + STROBE_VALUE;
    if (fAlpha > 1.0f)
        fAlpha = 0.0f;

    bmpInfo.fAlpha = fAlpha;

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    // If the bitmap is currently disabled, this call will fail
    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
}


void ResetBitmapAlpha(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    if (!pBMP)
        return;

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Return bitmap alpha to its default value
    bmpInfo.fAlpha = TRANSPARENCY_VALUE;

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);
}


void HandleSlide(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Slowly decrease the X coordinate
    bmpInfo.rDest.left  -= SLIDE_VALUE;   
    bmpInfo.rDest.right -= SLIDE_VALUE;

    // Once the bitmap disappears off the left side of the screen,
    // reset to the rightmost side of the window.
    // Take into account that the bitmap might be mirrored, in which case
    // the left/right coordinates are switched.

    if ((g_dwWatermarkFlags & MARK_MIRROR) == 0)
    {
        // NOT mirrored
        if (bmpInfo.rDest.right <= EDGE_BUFFER)
        {
            bmpInfo.rDest.left = 1.0f;
            bmpInfo.rDest.right = 1.0f + g_fBitmapCompWidth;
        }
    }
    else
    {
        // Mirrored
        if (bmpInfo.rDest.left <= EDGE_BUFFER)
        {
            bmpInfo.rDest.right = 1.0f;
            bmpInfo.rDest.left = 1.0f + g_fBitmapCompWidth;
        }
    }

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    // If the bitmap is currently disabled, this call will fail
    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
}


void ResetBitmapPosition(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    if (!pBMP)
        return;

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Return bitmap position to its original value.
    // Since the image may be currently mirrored, swap the
    // left/right coordinates if necessary
    if (g_dwWatermarkFlags & MARK_MIRROR)
    {
        // Mirrored
        bmpInfo.rDest.left  = g_rDest.right;
        bmpInfo.rDest.right = g_rDest.left;
    }
    else
    {
        // NOT mirrored
        bmpInfo.rDest.left  = g_rDest.left;
        bmpInfo.rDest.right = g_rDest.right;
    }

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);
}


void HandleAnimation(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};
    static int nCycle=0;

    // Fill the rDest and fAlpha values in bmpInfo
    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Move the image source to the right by one image width
    bmpInfo.rSrc.left   = g_nImageWidth * nCycle;
    bmpInfo.rSrc.right  = bmpInfo.rSrc.left + g_nImageWidth;
    bmpInfo.rSrc.top    = g_rSrc.top;
    bmpInfo.rSrc.bottom = g_rSrc.bottom;
    nCycle++;

    // If we have passed the last image in the wide bitmap,
    // then reset to the default source location (leftmost image)
    if (bmpInfo.rSrc.left >= g_nBitmapWidth)
    {
        bmpInfo.rSrc = g_rSrcSingle;
        nCycle = 1;
    }

    // Set the necessary flag to update the source rectangle
    bmpInfo.dwFlags = VMRBITMAP_SRCRECT;

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    // Give the VMR a new bitmap to display
    // If the bitmap is currently disabled, this call will fail
    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
}


void ResetAnimation(void)
{
    HRESULT hr;
    VMRALPHABITMAP bmpInfo={0};

    if (!pBMP)
        return;

    hr = pBMP->GetAlphaBitmapParameters(&bmpInfo);

    // Update the source rectangle to the original single-image default
    bmpInfo.rSrc = g_rSrcSingle;

    // Set the necessary flag to update the source rectangle
    bmpInfo.dwFlags = VMRBITMAP_SRCRECT;

    // Set the COLORREF so that the bitmap outline will be transparent
    SetColorRef(bmpInfo);

    hr = pBMP->UpdateAlphaBitmapParameters(&bmpInfo);
    if (FAILED(hr))
        Msg(TEXT("UpdateAlphaBitmapParameters FAILED!  hr=0x%x\r\n"), hr);
}


void SetColorRef(VMRALPHABITMAP& bmpInfo)
{
    // Set the COLORREF so that the bitmap outline will be transparent
    bmpInfo.clrSrcKey = RGB(255, 255, 255);       // Pure white
    bmpInfo.dwFlags |= VMRBITMAP_SRCCOLORKEY;
}


void SetAllEffects(void)
{
    // Activate any effects that aren't currently active
    if ((g_dwWatermarkFlags & MARK_ANIMATE) == 0)
    {
        FlipFlag(MARK_ANIMATE);
        AnimateWatermark(g_dwWatermarkFlags);
    }
    if ((g_dwWatermarkFlags & MARK_STROBE) == 0)
    {
        FlipFlag(MARK_STROBE);
        StrobeWatermark(g_dwWatermarkFlags);
    }
    if ((g_dwWatermarkFlags & MARK_SLIDE) == 0)
    {
        FlipFlag(MARK_SLIDE);
        SlideWatermark(g_dwWatermarkFlags);
    }
}


void ClearAllEffects(void)
{
    KillTimer();

    // Deactivate all active effects
    if (g_dwWatermarkFlags & MARK_ANIMATE)
    {
        FlipFlag(MARK_ANIMATE);
        AnimateWatermark(g_dwWatermarkFlags);
    }
    if (g_dwWatermarkFlags & MARK_STROBE)
    {
        FlipFlag(MARK_STROBE);
        StrobeWatermark(g_dwWatermarkFlags);
    }
    if (g_dwWatermarkFlags & MARK_SLIDE)
    {
        FlipFlag(MARK_SLIDE);
        SlideWatermark(g_dwWatermarkFlags);
    }
}
