// Canvas.cpp : Implementation of cCanvas #include "stdafx.h" #include "Inject.h" #include "Canvas.h" #include "Manager.h" ///////////////////////////////////////////////////////////////////////////// // cCanvas cCanvas::~cCanvas() { _ASSERTE( m_hdc == NULL ); if( m_rgn != NULL ) ::DeleteObject( m_rgn ); } void cCanvas::testSurface() { if( m_pSurface.p == NULL ) cManager::_p->createSurface( &m_sz, &m_pSurface ); else if( m_pSurface->IsLost() != DD_OK ) m_pSurface->Restore(); } STDMETHODIMP cCanvas::PushClipRect(LPRECT prc, VARIANT_BOOL *pbVisible) { _ASSERTE( prc != NULL ); _ASSERTE( prc->right >= prc->left && prc->bottom >= prc->top ); _ASSERTE( pbVisible != NULL ); ClipParams ¤t = m_clipping.top(); if( !current.visible ) { // Not visible means just push and hope for a pop m_clipping.push( current ); return S_OK; } int nChildLeft = prc->left + current.window.left - current.org.x, nChildTop = prc->top + current.window.top - current.org.y, nChildWidth = prc->right - prc->left, nChildHeight = prc->bottom - prc->top; ClipParams p = { { nChildLeft, nChildTop, nChildLeft + nChildWidth, nChildTop + nChildHeight }, { 0, 0 }, VARIANT_TRUE }; // Clip the four sides of the rectangle if( p.window.left < current.window.left ) { // Clip left edge p.org.x += current.window.left - p.window.left; p.window.left = current.window.left; } if( p.window.top < current.window.top ) { // Clip top edge p.org.y += current.window.top - p.window.top; p.window.top = current.window.top; } if( p.window.right > current.window.right ) // Clip right edge p.window.right = current.window.right; if( p.window.bottom > current.window.bottom ) // Clip bottom edge p.window.bottom = current.window.bottom; // Check for clipping out of existance if( p.window.left >= p.window.right || p.window.top >= p.window.bottom ) // This rectangle is no longer visible p.visible = VARIANT_FALSE; // Return the remainder of the rectangle *pbVisible = p.visible; m_clipping.push( p ); return S_OK; } STDMETHODIMP cCanvas::PopClipRect() { _ASSERTE( m_clipping.size() > 1 ); m_clipping.pop(); return S_OK; } STDMETHODIMP cCanvas::GetDC(HDC *pdc) { _ASSERTE( pdc != NULL ); testSurface(); if( m_hdc != NULL ) // Already checked out return E_FAIL; m_pSurface->GetDC( &m_hdc ); // Create a clipping region ClipParams ¶m = m_clipping.top(); if( m_rgn == NULL ) m_rgn = ::CreateRectRgnIndirect( ¶m.window ); else ::SetRectRgn( m_rgn, param.window.left, param.window.top, param.window.right, param.window.bottom ); ::SelectObject( m_hdc, m_rgn ); // Set the origin ::SetWindowOrgEx( m_hdc, -param.window.left + param.org.x, -param.window.top + param.org.y, NULL ); *pdc = m_hdc; return S_OK; } STDMETHODIMP cCanvas::ReleaseDC() { if( m_hdc == NULL ) return E_FAIL; m_pSurface->ReleaseDC( m_hdc ); m_hdc = NULL; return S_OK; } STDMETHODIMP cCanvas::GetSurface(REFIID iid, void **ppvItf) { _ASSERTE( iid == IID_IDirectDrawSurface4 ); _ASSERTE( ppvItf != NULL ); testSurface(); return m_pSurface->QueryInterface( iid, ppvItf ); } STDMETHODIMP cCanvas::GetClipParams(ClipParams *pParams) { _ASSERTE( pParams != NULL ); *pParams = m_clipping.top(); return S_OK; } STDMETHODIMP cCanvas::Fill(LPRECT prc, long nRGB) { _ASSERTE( prc != NULL ); _ASSERTE( prc->right > prc->left && prc->bottom > prc->top ); testSurface(); VARIANT_BOOL bVisible; PushClipRect( prc, &bVisible ); if( bVisible ) { DDBLTFX fx; ::memset( &fx, 0, sizeof( DDBLTFX ) ); fx.dwSize = sizeof( DDBLTFX ); DownMixRGB((BYTE)nRGB, (BYTE)(nRGB >> 8), (BYTE)(nRGB >> 16), (WORD *)&fx.dwFillColor); m_pSurface->Blt( &m_clipping.top().window, NULL, NULL, DDBLT_COLORFILL, &fx ); } PopClipRect(); return S_OK; } STDMETHODIMP cCanvas::Frame(LPRECT prc, long nRGB) { _ASSERTE( prc != NULL ); _ASSERTE( prc->right > prc->left && prc->bottom > prc->top ); testSurface(); // TODO: Add your implementation code here return S_OK; } STDMETHODIMP cCanvas::get_WasLost(VARIANT_BOOL *pVal) { _ASSERTE( pVal != NULL ); *pVal = VARIANT_FALSE; if( m_pSurface.p != NULL ) { if( m_pSurface->IsLost() != DD_OK ) *pVal = VARIANT_TRUE; } else *pVal = VARIANT_TRUE; return S_OK; } STDMETHODIMP cCanvas::get_Size(LPSIZE pVal) { _ASSERTE( pVal != NULL ); *pVal = m_sz; return S_OK; } STDMETHODIMP cCanvas::put_Size(LPSIZE newVal) { _ASSERTE( newVal->cx > 0 && newVal->cy > 0 ); _ASSERTE( m_clipping.size() == 1 ); if( m_sz.cx != newVal->cx || m_sz.cy != newVal->cy ) { m_sz = *newVal; if( m_pSurface.p ) // Next frame this'll be marked as lost m_pSurface.Release(); // Reset the bounds in the root clip rectangle ClipParams &clip = m_clipping.top(); clip.window.right = clip.window.left + newVal->cx; clip.window.bottom = clip.window.top + newVal->cy; } return S_OK; } STDMETHODIMP cCanvas::Blt(LPRECT prcSrc, ICanvas *pSrc, LPPOINT pptDest) { _ASSERTE( pSrc != NULL ); _ASSERTE( prcSrc != NULL ); _ASSERTE( prcSrc->right > prcSrc->left && prcSrc->bottom > prcSrc->top ); _ASSERTE( pptDest != NULL ); testSurface(); // Ok, first, make up the clip rect RECT rc = { pptDest->x, pptDest->y, pptDest->x + ( prcSrc->right - prcSrc->left ), pptDest->y + ( prcSrc->bottom - prcSrc->top ) }; VARIANT_BOOL bVisible; PushClipRect( &rc, &bVisible ); if( bVisible ) { CComPtr< IDirectDrawSurface4 > pSurf; pSrc->GetSurface( IID_IDirectDrawSurface4, reinterpret_cast< void ** >( &pSurf ) ); _ASSERTE( pSurf.p != NULL ); _ASSERTE( pSurf->IsLost() == DD_OK ); // Calculate the metrics #ifdef _DEBUG SIZE sz; pSrc->get_Size( &sz ); _ASSERTE( prcSrc->left >= 0 && prcSrc->top >= 0 && prcSrc->right <= sz.cx && prcSrc->bottom <= sz.cy ); #endif ClipParams ¤t = m_clipping.top(); RECT rcSrcClipped = { prcSrc->left + current.org.x, prcSrc->top + current.org.y, prcSrc->left + current.org.x + ( current.window.right - current.window.left ), prcSrc->top + current.org.y + ( current.window.bottom - current.window.top ) }; HRESULT hRes = S_OK; if ( sourceAlpha < 255) { if( cManager::_p->m_eAlphaBlendMode == eAlphaBlendSoftware ) { // Software alpha blending // It's actually plausible to implement this entirely using D3D and thus get hardware acceleration // Perhaps we should detect device caps and use a suitable method? // This should work on every Decal configuration though (except bank switched video cards, but who has them? :) //Lock and calculate metrics for destination DDSURFACEDESC2 ddsd; memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); int left = current.window.left; int top = current.window.top; int width = rcSrcClipped.right - rcSrcClipped.left; int height = rcSrcClipped.bottom - rcSrcClipped.top; if (FAILED(m_pSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_NOSYSLOCK, NULL))) _ASSERTE("Failed to destination lock surface!"); WORD *dest = (WORD *)ddsd.lpSurface; int iPitch = ddsd.lPitch / 2; int initialJump = (top * iPitch) + left; int jump = (iPitch - (width + left)) + left + width; //Lock and calculate metrics for source DDSURFACEDESC2 srcddsd; memset(&srcddsd, 0, sizeof(srcddsd)); srcddsd.dwSize = sizeof(srcddsd); if (FAILED(pSurf->Lock(NULL, &srcddsd, DDLOCK_WAIT | DDLOCK_NOSYSLOCK, NULL))) _ASSERTE("Failed to lock source surface!"); WORD *src = (WORD *)srcddsd.lpSurface; //Do it int iSrcPitch = srcddsd.lPitch / 2; int alpha = sourceAlpha; int ialpha = 255 - alpha; WORD *srcLine = new WORD[width]; WORD *destLine = new WORD[width]; WORD sB, dB, sG, dG, sR, dR; dest += initialJump; for (int y = 0; y < height; y++) { // We'll read 1 line at a time - 80% of the speed decrease here is reading/writing against the surface memcpy((void *)srcLine, src, width * 2); memcpy((void *)destLine, dest, width * 2); for (int x = 0; x < width; x++) { // This method is very fast, but assumes a constant 128 alpha // destLine[x] = ( (srcLine[x] & 0xF7DE) >> 1) + ( (destLine[x] & 0xF7DE) >> 1); // Basically ((alpha * (src - dest) / MAXALPHA) + dest) for each component // There's some significant room for MMX parrallelism here (~20% more FPS, methinks), but do we want to // be writing ASM? sB = srcLine[x] & ddsd.ddpfPixelFormat.dwBBitMask; dB = destLine[x] & ddsd.ddpfPixelFormat.dwBBitMask; sG = srcLine[x] & ddsd.ddpfPixelFormat.dwGBitMask; dG = destLine[x] & ddsd.ddpfPixelFormat.dwGBitMask; sR = srcLine[x] & ddsd.ddpfPixelFormat.dwRBitMask; dR = destLine[x] & ddsd.ddpfPixelFormat.dwRBitMask; destLine[x] = ddsd.ddpfPixelFormat.dwBBitMask & ((alpha * (sB - dB) >> 8) + dB) | ddsd.ddpfPixelFormat.dwGBitMask & (((alpha * (sG - dG) >> 8) + dG)) | ddsd.ddpfPixelFormat.dwRBitMask & (((alpha * (sR - dR) >> 8) + dR)); // The below is eversoslightly faster by assuming RGB 565 surface exposure. Some stupid old cards // blow up however. Fiddle if you like, YMMV. I'm sticking with something that works :) //sB = srcLine[x] & 0x1f; //dB = destLine[x] & 0x1f; //sG = (srcLine[x] >> 5) & 0x3f; //dG = (destLine[x] >> 5) & 0x3f; //sR = (srcLine[x] >> 11) & 0x1f; //dR = (destLine[x] >> 11) & 0x1f; //destLine[x] = ((alpha * (sB - dB) >> 8) + dB) | // (((alpha * (sG - dG) >> 8) + dG) << 5) | // (((alpha * (sR - dR) >> 8) + dR) << 11);*/ } memcpy(dest, destLine, width * 2); src += iSrcPitch; dest += jump; } //Release delete srcLine; delete destLine; if (FAILED(pSurf->Unlock(NULL))) _ASSERTE("Failed to unlock source surface!"); if (FAILED(m_pSurface->Unlock(NULL))) _ASSERTE("Failed to unlock destination surface!"); } else if ((cManager::_p->m_eAlphaBlendMode == eAlphaBlendGDIPlus) && (AlphaBlendF!=NULL)) { HDC hdcd; HDC hdcs; GetDC(&hdcd); pSrc->GetDC(&hdcs); hRes = AlphaBlendF(hdcd, current.org.x, current.org.y, rcSrcClipped.right-rcSrcClipped.left, rcSrcClipped.bottom-rcSrcClipped.top, hdcs, current.org.x, current.org.y, rcSrcClipped.right-rcSrcClipped.left, rcSrcClipped.bottom-rcSrcClipped.top, m_bf); pSrc->ReleaseDC(); ReleaseDC(); } } else { hRes = m_pSurface->BltFast( current.window.left, current.window.top, pSurf, &rcSrcClipped, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); } /* if(!SUCCEEDED(hRes)) { POINT pt = { 0, 0 }; ClientToScreen(cManager::_p->m_hMain, &pt); /*if((pt.x==0) || (pt.y==0)) MessageBox(0,"asd","asd",0);*/ //RECT rcd = {pt.x, pt.y, rcSrcClipped.right-rcSrcClipped.left, rcSrcClipped.bottom-rcSrcClipped.top}; //m_pSurface->Blt(&rcd,pSurf,&rcSrcClipped,DDBLT_WAIT,NULL); // RECT rcd = {0, 0, 640, 480}; /* rcSrcClipped.left+=current.window.left+pt.x; rcSrcClipped.right+=current.window.left+pt.x; rcSrcClipped.top+=current.window.top+pt.y+28; rcSrcClipped.bottom+=current.window.top+pt.y+28; /* DDBLTFX ddbltfx; ddbltfx.dwSize=sizeof(ddbltfx); ddbltfx.dwFillColor=5; m_pSurface->Blt(&rcSrcClipped,NULL,NULL,DDBLT_COLORFILL,&ddbltfx);*/ /* CComPtr< ICanvas > pCan; cManager::_p->GetPrimarySurface(&pCan); CComPtr< IDirectDrawSurface4 > pDDS4; pCan->GetSurface( IID_IDirectDrawSurface4, reinterpret_cast< void ** >( &pDDS4 ) ); // if(m_pSurface.p==pDDS4.p) if(cManager::_p->m_lpSurface!=NULL) { MessageBeep(0); DDSURFACEDESC2 ddesc; ddesc.dwSize=sizeof(DDSURFACEDESC2); pSurf->Lock(&rcSrcClipped, &ddesc, DDLOCK_SURFACEMEMORYPTR , NULL); //m_pSurface->Blt(&rcSrcClipped,pSurf,NULL,DDBLT_KEYSRC | DDBLT_WAIT ,NULL); memcpy(cManager::_p->m_lpSurface, ddesc.lpSurface, 1000); pSurf->Unlock(&rcSrcClipped); SetEvent(cManager::_p->m_hDrawSync); } //m_pSurface->Blt(&rcSrcClipped,pSurf,NULL,DDBLT_KEYSRC | DDBLT_WAIT ,NULL); /* RECT rc; ::GetWindowRect( cManager::_p->m_hMain, &rc );*/ /* HDC hdcd; HDC hdcs; m_pSurface->GetDC(&hdcd); pSurf->GetDC(&hdcs); BitBlt(hdcd, pt.x+current.window.left, pt.y+28+current.window.top, 332, current.window.bottom-current.window.top, hdcs, rcSrcClipped.left, rcSrcClipped.top, SRCCOPY); pSurf->ReleaseDC(hdcs); m_pSurface->ReleaseDC(hdcd);*/ /* hRes=0; } /*if(!SUCCEEDED(hRes)) SetEvent(cManager::_p->m_hDrawSync);*/ _ASSERTE( SUCCEEDED( hRes ) ); } PopClipRect(); _ASSERTMEM( _CrtCheckMemory() ); return S_OK; } STDMETHODIMP cCanvas::HitTest(LPPOINT ppt, VARIANT_BOOL *pbHit) { _ASSERTE( ppt != NULL ); _ASSERTE( pbHit != NULL ); // Assuming we're in screen coords ClipParams &clip = m_clipping.top(); if( ppt->x >= clip.window.left && ppt->y >= clip.window.top && ppt->x <= clip.window.right && ppt->y <= clip.window.bottom ) { *pbHit = VARIANT_TRUE; } else *pbHit = VARIANT_FALSE; return S_OK; } STDMETHODIMP cCanvas::ToClient(LPPOINT pt) { _ASSERTE( pt != NULL ); ClipParams &clip = m_clipping.top(); pt->x -= clip.window.left - clip.org.x; pt->y -= clip.window.top - clip.org.y; return S_OK; } STDMETHODIMP cCanvas::ToScreen(LPPOINT ppt) { _ASSERTE( ppt != NULL ); ClipParams &clip = m_clipping.top(); ppt->x += clip.window.left - clip.org.x; ppt->y += clip.window.top - clip.org.y; return S_OK; } STDMETHODIMP cCanvas::OffsetOrg(LPPOINT ppt, VARIANT_BOOL *pbVisible) { _ASSERTE( ppt != NULL ); _ASSERTE( pbVisible != NULL ); _ASSERTE( m_clipping.size() != 1 ); ClipParams &clip = m_clipping.top(); clip.org.x += ppt->x; clip.org.y += ppt->y; *pbVisible = clip.visible; return S_OK; } STDMETHODIMP cCanvas::SetClipRect(LPRECT prc, VARIANT_BOOL *pbVisible) { _ASSERTE( prc != NULL ); _ASSERTE( prc->right >= prc->left && prc->bottom >= prc->top ); _ASSERTE( pbVisible != NULL ); _ASSERTE( m_clipping.size() != 1 ); ClipParams ¤t = m_clipping.top(); int nChildLeft = prc->left + current.window.left - current.org.x, nChildTop = prc->top + current.window.top - current.org.y, nChildWidth = prc->right - prc->left, nChildHeight = prc->bottom - prc->top; ClipParams p = { { nChildLeft, nChildTop, nChildLeft + nChildWidth, nChildTop + nChildHeight }, { 0, 0 }, VARIANT_TRUE }; // Clip the four sides of the rectangle if( p.window.left < current.window.left ) { // Clip left edge p.org.x += current.window.left - p.window.left; p.window.left = current.window.left; } if( p.window.top < current.window.top ) { // Clip top edge p.org.y += current.window.top - p.window.top; p.window.top = current.window.top; } if( p.window.right > current.window.right ) // Clip right edge p.window.right = current.window.right; if( p.window.bottom > current.window.bottom ) // Clip bottom edge p.window.bottom = current.window.bottom; // Check for clipping out of existance if( p.window.left >= p.window.right || p.window.top >= p.window.bottom ) // This rectangle is no longer visible p.visible = VARIANT_FALSE; // Return the remainder of the rectangle *pbVisible = p.visible; m_clipping.top() = p; return S_OK; } STDMETHODIMP cCanvas::put_Alpha(long Alpha) { m_bf.SourceConstantAlpha = sourceAlpha = Alpha; return S_OK; } // processMask - Support function for DownMixRGB void cCanvas::processMask(WORD bitMask, WORD *loBit, WORD *bitCount) { _ASSERTE(loBit); _ASSERTE(bitCount); WORD mask = 1; for (*loBit = 0; *loBit < 16; (*loBit)++) { if (bitMask & mask) break; mask <<= 1; } for (*bitCount = 1; *bitCount < 32; (*bitCount)++) { mask <<= 1; if (!(bitMask & mask)) break; } } // DownMixRGB - Returns a 16 bit RGB value suitable for this canvas from a standard R, G, B set STDMETHODIMP cCanvas::DownMixRGB(WORD wRed, WORD wGreen, WORD wBlue, WORD *wDMRGB) { _ASSERTE(wDMRGB); if (wRBitCount == 0) { // We've never obtained surface information before for this canvas DDSURFACEDESC2 desc; desc.dwSize = sizeof( DDSURFACEDESC2 ); if (FAILED(m_pSurface->GetSurfaceDesc(&desc))) return S_FALSE; processMask(desc.ddpfPixelFormat.dwRBitMask, &wRLoBit, &wRBitCount); processMask(desc.ddpfPixelFormat.dwGBitMask, &wGLoBit, &wGBitCount); processMask(desc.ddpfPixelFormat.dwBBitMask, &wBLoBit, &wBBitCount); } // Transparent color handling if (wRed == 0 && wGreen == 255 && wBlue == 255) { // We are a transparent colour - The downmix would've destroyed it *wDMRGB = 0x07FF; } else { // Safe for a normal mix *wDMRGB = ((wRed * (1 << wRBitCount)) / 256) << wRLoBit | ((wGreen * (1 << wGBitCount)) / 256) << wGLoBit | ((wBlue * (1 << wBBitCount)) / 256) << wBLoBit; } return S_OK; } STDMETHODIMP cCanvas::GetDCLong(long *DC) { HDC pdc; GetDC(&pdc); *DC = reinterpret_cast< long >( pdc ); return S_OK; } STDMETHODIMP cCanvas::SetTransparentColor(long TransColor) { unsigned short wDownmix; DownMixRGB((BYTE)TransColor, (BYTE)(TransColor >> 8), (BYTE)(TransColor >> 16), &wDownmix ); _DDCOLORKEY colorkey; colorkey.dwColorSpaceHighValue = wDownmix; colorkey.dwColorSpaceLowValue = wDownmix; m_pSurface->SetColorKey( DDCKEY_SRCBLT, &colorkey ); return S_OK; } STDMETHODIMP cCanvas::StretchBlt(LPRECT prcSrc, ICanvas *pSrc, LPRECT prcDest) { _ASSERTE( pSrc != NULL ); _ASSERTE( prcSrc != NULL ); _ASSERTE( prcSrc->right > prcSrc->left && prcSrc->bottom > prcSrc->top ); //_ASSERTE( pptDest != NULL ); testSurface(); //RECT rc = { pptDest->x, pptDest->y, pptDest->x + ( prcSrc->right - prcSrc->left ), pptDest->y + ( prcSrc->bottom - prcSrc->top ) }; // Ok, first, make up the clip rect RECT rc = { prcDest->left, prcDest->top, prcDest->right, prcDest->bottom }; VARIANT_BOOL bVisible = TRUE; PushClipRect( &rc, &bVisible ); //if( bVisible ) { CComPtr< IDirectDrawSurface4 > pSurf; pSrc->GetSurface( IID_IDirectDrawSurface4, reinterpret_cast< void ** >( &pSurf ) ); _ASSERTE( pSurf.p != NULL ); _ASSERTE( pSurf->IsLost() == DD_OK ); // Calculate the metrics #ifdef _DEBUG SIZE sz; pSrc->get_Size( &sz ); _ASSERTE( prcSrc->left >= 0 && prcSrc->top >= 0 && prcSrc->right <= sz.cx && prcSrc->bottom <= sz.cy ); #endif ClipParams ¤t = m_clipping.top(); RECT rcSrcClipped = { prcSrc->left, prcSrc->top, prcSrc->right,// + ( current.window.right - current.window.left ), prcSrc->bottom// + ( current.window.bottom - current.window.top ) }; RECT rcDestClipped = { prcDest->left + current.org.x + current.window.left, prcDest->top + current.org.y + current.window.top, prcDest->left + prcDest->right + current.org.x, prcDest->top + prcDest->bottom + current.org.y }; HRESULT hRes = m_pSurface->Blt( &rcDestClipped, pSurf, &rcSrcClipped, DDBLT_KEYSRC | DDBLT_WAIT, NULL ); _ASSERTE( SUCCEEDED( hRes ) ); } PopClipRect(); _ASSERTMEM( _CrtCheckMemory() ); return S_OK; }