Aegisub/vsfilter/subpic/ISubPic.cpp

860 lines
17 KiB
C++

/*
* Copyright (C) 2003-2006 Gabest
* http://www.gabest.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
#include "stdafx.h"
#include "ISubPic.h"
#include "..\DSUtil\DSUtil.h"
//
// ISubPicImpl
//
ISubPicImpl::ISubPicImpl()
: CUnknown(NAME("ISubPicImpl"), NULL)
, m_rtStart(0), m_rtStop(0)
, m_rcDirty(0, 0, 0, 0), m_maxsize(0, 0), m_size(0, 0), m_vidrect(0, 0, 0, 0)
{
}
STDMETHODIMP ISubPicImpl::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(ISubPic)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPic
STDMETHODIMP_(REFERENCE_TIME) ISubPicImpl::GetStart()
{
return(m_rtStart);
}
STDMETHODIMP_(REFERENCE_TIME) ISubPicImpl::GetStop()
{
return(m_rtStop);
}
STDMETHODIMP_(void) ISubPicImpl::SetStart(REFERENCE_TIME rtStart)
{
m_rtStart = rtStart;
}
STDMETHODIMP_(void) ISubPicImpl::SetStop(REFERENCE_TIME rtStop)
{
m_rtStop = rtStop;
}
STDMETHODIMP ISubPicImpl::CopyTo(ISubPic* pSubPic)
{
if(!pSubPic)
return E_POINTER;
pSubPic->SetStart(m_rtStart);
pSubPic->SetStop(m_rtStop);
pSubPic->SetDirtyRect(m_rcDirty);
pSubPic->SetSize(m_size, m_vidrect);
return S_OK;
}
STDMETHODIMP ISubPicImpl::GetDirtyRect(RECT* pDirtyRect)
{
return pDirtyRect ? *pDirtyRect = m_rcDirty, S_OK : E_POINTER;
}
STDMETHODIMP ISubPicImpl::SetDirtyRect(RECT* pDirtyRect)
{
return pDirtyRect ? m_rcDirty = *pDirtyRect, S_OK : E_POINTER;
}
STDMETHODIMP ISubPicImpl::GetMaxSize(SIZE* pMaxSize)
{
return pMaxSize ? *pMaxSize = m_maxsize, S_OK : E_POINTER;
}
STDMETHODIMP ISubPicImpl::SetSize(SIZE size, RECT vidrect)
{
m_size = size;
m_vidrect = vidrect;
if(m_size.cx > m_maxsize.cx)
{
m_size.cy = MulDiv(m_size.cy, m_maxsize.cx, m_size.cx);
m_size.cx = m_maxsize.cx;
}
if(m_size.cy > m_maxsize.cy)
{
m_size.cx = MulDiv(m_size.cx, m_maxsize.cy, m_size.cy);
m_size.cy = m_maxsize.cy;
}
if(m_size.cx != size.cx || m_size.cy != size.cy)
{
m_vidrect.top = MulDiv(m_vidrect.top, m_size.cx, size.cx);
m_vidrect.bottom = MulDiv(m_vidrect.bottom, m_size.cx, size.cx);
m_vidrect.left = MulDiv(m_vidrect.left, m_size.cy, size.cy);
m_vidrect.right = MulDiv(m_vidrect.right, m_size.cy, size.cy);
}
return S_OK;
}
//
// ISubPicAllocatorImpl
//
ISubPicAllocatorImpl::ISubPicAllocatorImpl(SIZE cursize, bool fDynamicWriteOnly, bool fPow2Textures)
: CUnknown(NAME("ISubPicAllocatorImpl"), NULL)
, m_cursize(cursize)
, m_fDynamicWriteOnly(fDynamicWriteOnly)
, m_fPow2Textures(fPow2Textures)
{
m_curvidrect = CRect(CPoint(0,0), m_cursize);
}
STDMETHODIMP ISubPicAllocatorImpl::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(ISubPicAllocator)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPicAllocator
STDMETHODIMP ISubPicAllocatorImpl::SetCurSize(SIZE cursize)
{
m_cursize = cursize;
return S_OK;
}
STDMETHODIMP ISubPicAllocatorImpl::SetCurVidRect(RECT curvidrect)
{
m_curvidrect = curvidrect;
return S_OK;
}
STDMETHODIMP ISubPicAllocatorImpl::GetStatic(ISubPic** ppSubPic)
{
if(!ppSubPic)
return E_POINTER;
if(!m_pStatic)
{
if(!Alloc(true, &m_pStatic) || !m_pStatic)
return E_OUTOFMEMORY;
}
m_pStatic->SetSize(m_cursize, m_curvidrect);
(*ppSubPic = m_pStatic)->AddRef();
return S_OK;
}
STDMETHODIMP ISubPicAllocatorImpl::AllocDynamic(ISubPic** ppSubPic)
{
if(!ppSubPic)
return E_POINTER;
if(!Alloc(false, ppSubPic) || !*ppSubPic)
return E_OUTOFMEMORY;
(*ppSubPic)->SetSize(m_cursize, m_curvidrect);
return S_OK;
}
STDMETHODIMP_(bool) ISubPicAllocatorImpl::IsDynamicWriteOnly()
{
return(m_fDynamicWriteOnly);
}
STDMETHODIMP ISubPicAllocatorImpl::ChangeDevice(IUnknown* pDev)
{
m_pStatic = NULL;
return S_OK;
}
//
// ISubPicProviderImpl
//
ISubPicProviderImpl::ISubPicProviderImpl(CCritSec* pLock)
: CUnknown(NAME("ISubPicProviderImpl"), NULL)
, m_pLock(pLock)
{
}
ISubPicProviderImpl::~ISubPicProviderImpl()
{
}
STDMETHODIMP ISubPicProviderImpl::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(ISubPicProvider)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPicProvider
STDMETHODIMP ISubPicProviderImpl::Lock()
{
return m_pLock ? m_pLock->Lock(), S_OK : E_FAIL;
}
STDMETHODIMP ISubPicProviderImpl::Unlock()
{
return m_pLock ? m_pLock->Unlock(), S_OK : E_FAIL;
}
//
// ISubPicQueueImpl
//
ISubPicQueueImpl::ISubPicQueueImpl(ISubPicAllocator* pAllocator, HRESULT* phr)
: CUnknown(NAME("ISubPicQueueImpl"), NULL)
, m_pAllocator(pAllocator)
, m_rtNow(0)
, m_fps(25.0)
{
if(phr) *phr = S_OK;
if(!m_pAllocator)
{
if(phr) *phr = E_FAIL;
return;
}
}
ISubPicQueueImpl::~ISubPicQueueImpl()
{
}
STDMETHODIMP ISubPicQueueImpl::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(ISubPicQueue)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// ISubPicQueue
STDMETHODIMP ISubPicQueueImpl::SetSubPicProvider(ISubPicProvider* pSubPicProvider)
{
CAutoLock cAutoLock(&m_csSubPicProvider);
// if(m_pSubPicProvider != pSubPicProvider)
{
m_pSubPicProvider = pSubPicProvider;
Invalidate();
}
return S_OK;
}
STDMETHODIMP ISubPicQueueImpl::GetSubPicProvider(ISubPicProvider** pSubPicProvider)
{
if(!pSubPicProvider)
return E_POINTER;
CAutoLock cAutoLock(&m_csSubPicProvider);
if(m_pSubPicProvider)
(*pSubPicProvider = m_pSubPicProvider)->AddRef();
return !!*pSubPicProvider ? S_OK : E_FAIL;
}
STDMETHODIMP ISubPicQueueImpl::SetFPS(double fps)
{
m_fps = fps;
return S_OK;
}
STDMETHODIMP ISubPicQueueImpl::SetTime(REFERENCE_TIME rtNow)
{
m_rtNow = rtNow;
return S_OK;
}
// private
HRESULT ISubPicQueueImpl::RenderTo(ISubPic* pSubPic, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double fps)
{
HRESULT hr = E_FAIL;
if(!pSubPic)
return hr;
CComPtr<ISubPicProvider> pSubPicProvider;
if(FAILED(GetSubPicProvider(&pSubPicProvider)) || !pSubPicProvider)
return hr;
if(FAILED(pSubPicProvider->Lock()))
return hr;
SubPicDesc spd;
if(SUCCEEDED(pSubPic->ClearDirtyRect(0xFF000000))
&& SUCCEEDED(pSubPic->Lock(spd)))
{
CRect r(0,0,0,0);
hr = pSubPicProvider->Render(spd, (rtStart+rtStop)/2, fps, r);
pSubPic->SetStart(rtStart);
pSubPic->SetStop(rtStop);
pSubPic->Unlock(r);
}
pSubPicProvider->Unlock();
return hr;
}
//
// CSubPicQueue
//
CSubPicQueue::CSubPicQueue(int nMaxSubPic, ISubPicAllocator* pAllocator, HRESULT* phr)
: ISubPicQueueImpl(pAllocator, phr)
, m_nMaxSubPic(nMaxSubPic)
, m_rtQueueStart(0)
{
if(phr && FAILED(*phr))
return;
if(m_nMaxSubPic < 1)
{if(phr) *phr = E_INVALIDARG; return;}
m_fBreakBuffering = false;
for(int i = 0; i < EVENT_COUNT; i++)
m_ThreadEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
CAMThread::Create();
}
CSubPicQueue::~CSubPicQueue()
{
m_fBreakBuffering = true;
SetEvent(m_ThreadEvents[EVENT_EXIT]);
CAMThread::Close();
for(int i = 0; i < EVENT_COUNT; i++)
CloseHandle(m_ThreadEvents[i]);
}
// ISubPicQueue
STDMETHODIMP CSubPicQueue::SetFPS(double fps)
{
HRESULT hr = __super::SetFPS(fps);
if(FAILED(hr)) return hr;
SetEvent(m_ThreadEvents[EVENT_TIME]);
return S_OK;
}
STDMETHODIMP CSubPicQueue::SetTime(REFERENCE_TIME rtNow)
{
HRESULT hr = __super::SetTime(rtNow);
if(FAILED(hr)) return hr;
SetEvent(m_ThreadEvents[EVENT_TIME]);
return S_OK;
}
STDMETHODIMP CSubPicQueue::Invalidate(REFERENCE_TIME rtInvalidate)
{
{
// CAutoLock cQueueLock(&m_csQueueLock);
// RemoveAll();
m_rtInvalidate = rtInvalidate;
m_fBreakBuffering = true;
SetEvent(m_ThreadEvents[EVENT_TIME]);
}
return S_OK;
}
STDMETHODIMP_(bool) CSubPicQueue::LookupSubPic(REFERENCE_TIME rtNow, ISubPic** ppSubPic)
{
if(!ppSubPic)
return(false);
*ppSubPic = NULL;
CAutoLock cQueueLock(&m_csQueueLock);
POSITION pos = GetHeadPosition();
while(pos)
{
CComPtr<ISubPic> pSubPic = GetNext(pos);
if(pSubPic->GetStart() <= rtNow && rtNow < pSubPic->GetStop())
{
*ppSubPic = pSubPic.Detach();
break;
}
}
return(!!*ppSubPic);
}
STDMETHODIMP CSubPicQueue::GetStats(int& nSubPics, REFERENCE_TIME& rtNow, REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
CAutoLock cQueueLock(&m_csQueueLock);
nSubPics = GetCount();
rtNow = m_rtNow;
rtStart = m_rtQueueStart;
rtStop = GetCount() > 0 ? GetTail()->GetStop() : rtStart;
return S_OK;
}
STDMETHODIMP CSubPicQueue::GetStats(int nSubPic, REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
CAutoLock cQueueLock(&m_csQueueLock);
rtStart = rtStop = -1;
if(nSubPic >= 0 && nSubPic < (int)GetCount())
{
if(POSITION pos = FindIndex(nSubPic))
{
rtStart = GetAt(pos)->GetStart();
rtStop = GetAt(pos)->GetStop();
}
}
else
{
return E_INVALIDARG;
}
return S_OK;
}
// private
REFERENCE_TIME CSubPicQueue::UpdateQueue()
{
CAutoLock cQueueLock(&m_csQueueLock);
REFERENCE_TIME rtNow = m_rtNow;
if(rtNow < m_rtQueueStart)
{
RemoveAll();
}
else
{
while(GetCount() > 0 && rtNow >= GetHead()->GetStop())
RemoveHead();
}
m_rtQueueStart = rtNow;
if(GetCount() > 0)
rtNow = GetTail()->GetStop();
return(rtNow);
}
void CSubPicQueue::AppendQueue(ISubPic* pSubPic)
{
CAutoLock cQueueLock(&m_csQueueLock);
AddTail(pSubPic);
}
// overrides
DWORD CSubPicQueue::ThreadProc()
{
SetThreadPriority(m_hThread, THREAD_PRIORITY_LOWEST/*THREAD_PRIORITY_BELOW_NORMAL*/);
while((WaitForMultipleObjects(EVENT_COUNT, m_ThreadEvents, FALSE, INFINITE) - WAIT_OBJECT_0) == EVENT_TIME)
{
double fps = m_fps;
REFERENCE_TIME rtNow = UpdateQueue();
int nMaxSubPic = m_nMaxSubPic;
CComPtr<ISubPicProvider> pSubPicProvider;
if(SUCCEEDED(GetSubPicProvider(&pSubPicProvider)) && pSubPicProvider
&& SUCCEEDED(pSubPicProvider->Lock()))
{
for(POSITION pos = pSubPicProvider->GetStartPosition(rtNow, fps);
pos && !m_fBreakBuffering && GetCount() < (size_t)nMaxSubPic;
pos = pSubPicProvider->GetNext(pos))
{
REFERENCE_TIME rtStart = pSubPicProvider->GetStart(pos, fps);
REFERENCE_TIME rtStop = pSubPicProvider->GetStop(pos, fps);
if(m_rtNow >= rtStart)
{
// m_fBufferUnderrun = true;
if(m_rtNow >= rtStop) continue;
}
if(rtStart >= m_rtNow + 60*10000000i64) // we are already one minute ahead, this should be enough
break;
if(rtNow < rtStop)
{
CComPtr<ISubPic> pStatic;
if(FAILED(m_pAllocator->GetStatic(&pStatic)))
break;
HRESULT hr = RenderTo(pStatic, rtStart, rtStop, fps);
if(FAILED(hr))
break;
if(S_OK != hr) // subpic was probably empty
continue;
CComPtr<ISubPic> pDynamic;
if(FAILED(m_pAllocator->AllocDynamic(&pDynamic))
|| FAILED(pStatic->CopyTo(pDynamic)))
break;
AppendQueue(pDynamic);
}
}
pSubPicProvider->Unlock();
}
if(m_fBreakBuffering)
{
CAutoLock cQueueLock(&m_csQueueLock);
REFERENCE_TIME rtInvalidate = m_rtInvalidate;
while(GetCount() && GetTail()->GetStop() > rtInvalidate)
{
if(GetTail()->GetStart() < rtInvalidate) GetTail()->SetStop(rtInvalidate);
else RemoveTail();
}
m_fBreakBuffering = false;
}
}
return(0);
}
//
// CSubPicQueueNoThread
//
CSubPicQueueNoThread::CSubPicQueueNoThread(ISubPicAllocator* pAllocator, HRESULT* phr)
: ISubPicQueueImpl(pAllocator, phr)
{
}
CSubPicQueueNoThread::~CSubPicQueueNoThread()
{
}
// ISubPicQueue
STDMETHODIMP CSubPicQueueNoThread::Invalidate(REFERENCE_TIME rtInvalidate)
{
CAutoLock cQueueLock(&m_csLock);
m_pSubPic = NULL;
return S_OK;
}
STDMETHODIMP_(bool) CSubPicQueueNoThread::LookupSubPic(REFERENCE_TIME rtNow, ISubPic** ppSubPic)
{
if(!ppSubPic)
return(false);
*ppSubPic = NULL;
CComPtr<ISubPic> pSubPic;
{
CAutoLock cAutoLock(&m_csLock);
if(!m_pSubPic)
{
if(FAILED(m_pAllocator->AllocDynamic(&m_pSubPic)))
return(false);
}
pSubPic = m_pSubPic;
}
if(pSubPic->GetStart() <= rtNow && rtNow < pSubPic->GetStop())
{
(*ppSubPic = pSubPic)->AddRef();
}
else
{
CComPtr<ISubPicProvider> pSubPicProvider;
if(SUCCEEDED(GetSubPicProvider(&pSubPicProvider)) && pSubPicProvider
&& SUCCEEDED(pSubPicProvider->Lock()))
{
double fps = m_fps;
if(POSITION pos = pSubPicProvider->GetStartPosition(rtNow, fps))
{
REFERENCE_TIME rtStart = pSubPicProvider->GetStart(pos, fps);
REFERENCE_TIME rtStop = pSubPicProvider->GetStop(pos, fps);
if(pSubPicProvider->IsAnimated(pos))
{
rtStart = rtNow;
rtStop = rtNow+1;
}
if(rtStart <= rtNow && rtNow < rtStop)
{
if(m_pAllocator->IsDynamicWriteOnly())
{
CComPtr<ISubPic> pStatic;
if(SUCCEEDED(m_pAllocator->GetStatic(&pStatic))
&& SUCCEEDED(RenderTo(pStatic, rtStart, rtStop, fps))
&& SUCCEEDED(pStatic->CopyTo(pSubPic)))
(*ppSubPic = pSubPic)->AddRef();
}
else
{
if(SUCCEEDED(RenderTo(m_pSubPic, rtStart, rtStop, fps)))
(*ppSubPic = pSubPic)->AddRef();
}
}
}
pSubPicProvider->Unlock();
if(*ppSubPic)
{
CAutoLock cAutoLock(&m_csLock);
m_pSubPic = *ppSubPic;
}
}
}
return(!!*ppSubPic);
}
STDMETHODIMP CSubPicQueueNoThread::GetStats(int& nSubPics, REFERENCE_TIME& rtNow, REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
CAutoLock cAutoLock(&m_csLock);
nSubPics = 0;
rtNow = m_rtNow;
rtStart = rtStop = 0;
if(m_pSubPic)
{
nSubPics = 1;
rtStart = m_pSubPic->GetStart();
rtStop = m_pSubPic->GetStop();
}
return S_OK;
}
STDMETHODIMP CSubPicQueueNoThread::GetStats(int nSubPic, REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
CAutoLock cAutoLock(&m_csLock);
if(!m_pSubPic || nSubPic != 0)
return E_INVALIDARG;
rtStart = m_pSubPic->GetStart();
rtStop = m_pSubPic->GetStop();
return S_OK;
}
//
// ISubPicAllocatorPresenterImpl
//
ISubPicAllocatorPresenterImpl::ISubPicAllocatorPresenterImpl(HWND hWnd, HRESULT& hr)
: CUnknown(NAME("ISubPicAllocatorPresenterImpl"), NULL)
, m_hWnd(hWnd)
, m_NativeVideoSize(0, 0), m_AspectRatio(0, 0)
, m_VideoRect(0, 0, 0, 0), m_WindowRect(0, 0, 0, 0)
, m_fps(25.0)
{
if(!IsWindow(m_hWnd)) {hr = E_INVALIDARG; return;}
GetWindowRect(m_hWnd, &m_WindowRect);
SetVideoAngle(Vector(), false);
hr = S_OK;
}
ISubPicAllocatorPresenterImpl::~ISubPicAllocatorPresenterImpl()
{
}
STDMETHODIMP ISubPicAllocatorPresenterImpl::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
return
QI(ISubPicAllocatorPresenter)
__super::NonDelegatingQueryInterface(riid, ppv);
}
void ISubPicAllocatorPresenterImpl::AlphaBltSubPic(CSize size, SubPicDesc* pTarget)
{
CComPtr<ISubPic> pSubPic;
if(m_pSubPicQueue->LookupSubPic(m_rtNow, &pSubPic))
{
SubPicDesc spd;
pSubPic->GetDesc(spd);
if(spd.w > 0 && spd.h > 0)
{
CRect r;
pSubPic->GetDirtyRect(r);
// FIXME
r.DeflateRect(1, 1);
CRect rDstText(
r.left * size.cx / spd.w,
r.top * size.cy / spd.h,
r.right * size.cx / spd.w,
r.bottom * size.cy / spd.h);
pSubPic->AlphaBlt(r, rDstText, pTarget);
}
}
}
// ISubPicAllocatorPresenter
STDMETHODIMP_(SIZE) ISubPicAllocatorPresenterImpl::GetVideoSize(bool fCorrectAR)
{
CSize VideoSize(m_NativeVideoSize);
if(fCorrectAR && m_AspectRatio.cx > 0 && m_AspectRatio.cy > 0)
VideoSize.cx = VideoSize.cy*m_AspectRatio.cx/m_AspectRatio.cy;
return(VideoSize);
}
STDMETHODIMP_(void) ISubPicAllocatorPresenterImpl::SetPosition(RECT w, RECT v)
{
bool fWindowPosChanged = !!(m_WindowRect != w);
bool fWindowSizeChanged = !!(m_WindowRect.Size() != CRect(w).Size());
m_WindowRect = w;
bool fVideoRectChanged = !!(m_VideoRect != v);
m_VideoRect = v;
if(fWindowSizeChanged || fVideoRectChanged)
{
if(m_pAllocator)
{
m_pAllocator->SetCurSize(m_WindowRect.Size());
m_pAllocator->SetCurVidRect(m_VideoRect);
}
if(m_pSubPicQueue)
{
m_pSubPicQueue->Invalidate();
}
}
if(fWindowPosChanged || fVideoRectChanged)
Paint(fWindowSizeChanged || fVideoRectChanged);
}
STDMETHODIMP_(void) ISubPicAllocatorPresenterImpl::SetTime(REFERENCE_TIME rtNow)
{
/*
if(m_rtNow <= rtNow && rtNow <= m_rtNow + 1000000)
return;
*/
m_rtNow = rtNow;
if(m_pSubPicQueue)
{
m_pSubPicQueue->SetTime(rtNow);
}
}
STDMETHODIMP_(double) ISubPicAllocatorPresenterImpl::GetFPS()
{
return(m_fps);
}
STDMETHODIMP_(void) ISubPicAllocatorPresenterImpl::SetSubPicProvider(ISubPicProvider* pSubPicProvider)
{
m_SubPicProvider = pSubPicProvider;
if(m_pSubPicQueue)
m_pSubPicQueue->SetSubPicProvider(pSubPicProvider);
}
STDMETHODIMP_(void) ISubPicAllocatorPresenterImpl::Invalidate(REFERENCE_TIME rtInvalidate)
{
if(m_pSubPicQueue)
m_pSubPicQueue->Invalidate(rtInvalidate);
}
#include <math.h>
void ISubPicAllocatorPresenterImpl::Transform(CRect r, Vector v[4])
{
v[0] = Vector(r.left, r.top, 0);
v[1] = Vector(r.right, r.top, 0);
v[2] = Vector(r.left, r.bottom, 0);
v[3] = Vector(r.right, r.bottom, 0);
Vector center(r.CenterPoint().x, r.CenterPoint().y, 0);
int l = (int)(Vector(r.Size().cx, r.Size().cy, 0).Length()*1.5f)+1;
for(int i = 0; i < 4; i++)
{
v[i] = m_xform << (v[i] - center);
v[i].z = v[i].z / l + 0.5f;
v[i].x /= v[i].z*2;
v[i].y /= v[i].z*2;
v[i] += center;
}
}
STDMETHODIMP ISubPicAllocatorPresenterImpl::SetVideoAngle(Vector v, bool fRepaint)
{
m_xform = XForm(Ray(Vector(0, 0, 0), v), Vector(1, 1, 1), false);
if(fRepaint) Paint(true);
return S_OK;
}