// SimpView.cpp : implementation of the CSimpsonsView class // #define DISABLE_CROSSDOT #include "stdafx.h" #include "simpsons.h" #include "SimpDoc.h" #include "SimpView.h" #include "dxtrans.h" #include "dxhelper.h" #include #define fZOOMFACTOR 0.03f #define fSCALEMIN 0.4f #define fSCALEMAX 2.5f #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Flatten to an error of 2/3. During initial phase, use 18.14 format. #define TEST_MAGNITUDE_INITIAL (6 * 0x00002aa0L) // Error of 2/3. During normal phase, use 15.17 format. #define TEST_MAGNITUDE_NORMAL (TEST_MAGNITUDE_INITIAL << 3) // 'FIX' is a 28.4 fixed point type: #define FIX_SCALE 16 typedef LONG FIX; typedef POINT POINTFIX; typedef struct _RECTFX { FIX xLeft; FIX yTop; FIX xRight; FIX yBottom; } RECTFX, *PRECTFX; #define MIN(A,B) ((A) < (B) ? (A) : (B)) #define MAX(A,B) ((A) > (B) ? (A) : (B)) #define ABS(A) ((A) < 0 ? -(A) : (A)) // Hacky macro which returns the current test case's attribute array #define ThisTestCase (TC::testCases[m_testCaseNumber]) // Test case combinations // // We enumerate every test case in a table so that we can cycle through // all of them. namespace TC { // Names for each test case attribute enum {At_Library, At_Source, At_Destination, At_Aliasing}; const char *AttributeStr[] = { "Library", "Source", "Dest", "Aliasing" }; const int NumAttributes = (sizeof(AttributeStr)/sizeof(AttributeStr[0])); typedef int TestCase[NumAttributes]; // For each attribute, names for each option: enum {Meta, GDIP, GDI}; // At_Library enum {Native, FromMetafile, CreatePoly, PathAPI}; // At_Source enum {Memory, Screen, ToMetafile}; // At_Destination enum {Aliased, Antialiased}; // At_Aliasing const char *OptionStr[NumAttributes][4] = { "Meta", "GDI+", "GDI", "", "Native", "Metafile", "CreatePoly", "PathAPI", "Memory", "Screen", "Metafile", "", "Aliased", "AA", "", "" }; // Supported options for each library: // // GDI+: At_Source - Native, FromMetafile, PathAPI // At_Destination - Memory, Screen, ToMetafile // At_Aliasing - Aliased, Antialiased // // Meta: At_Source - Native // At_Destination - Memory, Screen // At_Aliasing - Antialiased // // GDI: At_Source - PathAPI, CreatePoly, FromMetafile // At_Destination - Memory, Screen, ToMetafile // At_Aliasing - Aliased const TestCase testCases[] = { // Library Source Destination Aliasing GDIP, Native, Memory, Antialiased, GDIP, Native, Screen, Antialiased, GDIP, Native, Memory, Aliased, GDIP, Native, Screen, Aliased, GDIP, Native, ToMetafile, Antialiased, GDIP, FromMetafile, Memory, Antialiased, GDIP, PathAPI, Memory, Antialiased, Meta, Native, Memory, Antialiased, Meta, Native, Screen, Antialiased, GDI, CreatePoly, Memory, Aliased, GDI, CreatePoly, Screen, Aliased, GDI, CreatePoly, ToMetafile, Aliased, GDI, FromMetafile, Memory, Aliased, GDI, PathAPI, Memory, Aliased, }; const int numTestCases = (sizeof(testCases)/(sizeof(testCases[0]))); }; // Test results, used when we cycle automatically through all test combinations // Hack: This should be a member of CSimpsonView, but I kept it here to reduce // compile time when we add a test case. DWORD timingResults[TC::numTestCases]; // IncrementAttribute(int): Changes the rendering attributes // Advances to the next test case which is different in the given // attribute. Unless the attribute is TC::At_Library, will only advance to a // case which is identical in all other attributes. // // If there is none, doesn't do anything. // // Returns: false if the test case didn't change bool CSimpsonsView::IncrementAttribute(int attribute) { int startValue=m_testCaseNumber; int i; while (1) { // Increment the test case number, with wraparound m_testCaseNumber++; if (m_testCaseNumber >= TC::numTestCases) m_testCaseNumber = 0; // If we've returned to the case we started on, no suitable // case was found if (m_testCaseNumber == startValue) return false; // Continue searching if the attribute for this case is the same if (TC::testCases[startValue][attribute] == TC::testCases[m_testCaseNumber][attribute]) continue; // If we're incrementing the library attribute, we've found what // we need. if (attribute == TC::At_Library) break; // Otherwise, we need to continue if this case isn't identical // in the other attributes for (i=0; i> 13); } }; /**********************************Class***********************************\ * class BEZIER32 * * Bezier cracker. * * A hybrid cubic Bezier curve flattener based on KirkO's error factor. * Generates line segments fast without using the stack. Used to flatten * a path. * * For an understanding of the methods used, see: * * Kirk Olynyk, "..." * Goossen and Olynyk, "System and Method of Hybrid Forward * Differencing to Render Bezier Splines" * Lien, Shantz and Vaughan Pratt, "Adaptive Forward Differencing for * Rendering Curves and Surfaces", Computer Graphics, July 1987 * Chang and Shantz, "Rendering Trimmed NURBS with Adaptive Forward * Differencing", Computer Graphics, August 1988 * Foley and Van Dam, "Fundamentals of Interactive Computer Graphics" * * This algorithm is protected by U.S. patents 5,363,479 and 5,367,617. * * Public Interface: * * vInit(pptfx) - pptfx points to 4 control points of * Bezier. Current point is set to the first * point after the start-point. * BEZIER32(pptfx) - Constructor with initialization. * vGetCurrent(pptfx) - Returns current polyline point. * bCurrentIsEndPoint() - TRUE if current point is end-point. * vNext() - Moves to next polyline point. * * History: * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/ class BEZIER32 { public: LONG cSteps; HFDBASIS32 x; HFDBASIS32 y; RECTFX rcfxBound; BOOL bInit(POINTFIX* aptfx, RECTFX*); BOOL bNext(POINTFIX* pptfx); }; #define INLINE inline INLINE BOOL bIntersect(RECTFX* prcfx1, RECTFX* prcfx2) { BOOL bRet = (prcfx1->yTop <= prcfx2->yBottom && prcfx1->yBottom >= prcfx2->yTop && prcfx1->xLeft <= prcfx2->xRight && prcfx1->xRight >= prcfx2->xLeft); return(bRet); } INLINE VOID vBoundBox(POINTFIX* aptfx, RECTFX* prcfx) { if (aptfx[0].x >= aptfx[1].x) if (aptfx[2].x >= aptfx[3].x) { prcfx->xLeft = MIN(aptfx[1].x, aptfx[3].x); prcfx->xRight = MAX(aptfx[0].x, aptfx[2].x); } else { prcfx->xLeft = MIN(aptfx[1].x, aptfx[2].x); prcfx->xRight = MAX(aptfx[0].x, aptfx[3].x); } else if (aptfx[2].x <= aptfx[3].x) { prcfx->xLeft = MIN(aptfx[0].x, aptfx[2].x); prcfx->xRight = MAX(aptfx[1].x, aptfx[3].x); } else { prcfx->xLeft = MIN(aptfx[0].x, aptfx[3].x); prcfx->xRight = MAX(aptfx[1].x, aptfx[2].x); } if (aptfx[0].y >= aptfx[1].y) if (aptfx[2].y >= aptfx[3].y) { prcfx->yTop = MIN(aptfx[1].y, aptfx[3].y); prcfx->yBottom = MAX(aptfx[0].y, aptfx[2].y); } else { prcfx->yTop = MIN(aptfx[1].y, aptfx[2].y); prcfx->yBottom = MAX(aptfx[0].y, aptfx[3].y); } else if (aptfx[2].y <= aptfx[3].y) { prcfx->yTop = MIN(aptfx[0].y, aptfx[2].y); prcfx->yBottom = MAX(aptfx[1].y, aptfx[3].y); } else { prcfx->yTop = MIN(aptfx[0].y, aptfx[3].y); prcfx->yBottom = MAX(aptfx[1].y, aptfx[2].y); } } INLINE VOID HFDBASIS32::vInit(FIX p1, FIX p2, FIX p3, FIX p4) { // Change basis and convert from 28.4 to 18.14 format: e0 = (p1 ) << 10; e1 = (p4 - p1 ) << 10; e2 = (3 * (p2 - p3 - p3 + p4)) << 11; e3 = (3 * (p1 - p2 - p2 + p3)) << 11; } INLINE VOID HFDBASIS32::vLazyHalveStepSize(LONG cShift) { e2 = (e2 + e3) >> 1; e1 = (e1 - (e2 >> cShift)) >> 1; } INLINE VOID HFDBASIS32::vSteadyState(LONG cShift) { // We now convert from 18.14 fixed format to 15.17: e0 <<= 3; e1 <<= 3; register LONG lShift = cShift - 3; if (lShift < 0) { lShift = -lShift; e2 <<= lShift; e3 <<= lShift; } else { e2 >>= lShift; e3 >>= lShift; } } INLINE VOID HFDBASIS32::vHalveStepSize() { e2 = (e2 + e3) >> 3; e1 = (e1 - e2) >> 1; e3 >>= 2; } INLINE VOID HFDBASIS32::vDoubleStepSize() { e1 += e1 + e2; e3 <<= 2; e2 = (e2 << 3) - e3; } INLINE VOID HFDBASIS32::vTakeStep() { e0 += e1; register LONG lTemp = e2; e1 += lTemp; e2 += lTemp - e3; e3 = lTemp; } typedef struct _BEZIERCONTROLS { POINTFIX ptfx[4]; } BEZIERCONTROLS; BOOL BEZIER32::bInit( POINTFIX* aptfxBez, // Pointer to 4 control points RECTFX* prcfxClip) // Bound box of visible region (optional) { POINTFIX aptfx[4]; LONG cShift = 0; // Keeps track of 'lazy' shifts cSteps = 1; // Number of steps to do before reach end of curve vBoundBox(aptfxBez, &rcfxBound); *((BEZIERCONTROLS*) aptfx) = *((BEZIERCONTROLS*) aptfxBez); { register FIX fxOr; register FIX fxOffset; fxOffset = rcfxBound.xLeft; fxOr = (aptfx[0].x -= fxOffset); fxOr |= (aptfx[1].x -= fxOffset); fxOr |= (aptfx[2].x -= fxOffset); fxOr |= (aptfx[3].x -= fxOffset); fxOffset = rcfxBound.yTop; fxOr |= (aptfx[0].y -= fxOffset); fxOr |= (aptfx[1].y -= fxOffset); fxOr |= (aptfx[2].y -= fxOffset); fxOr |= (aptfx[3].y -= fxOffset); // This 32 bit cracker can only handle points in a 10 bit space: if ((fxOr & 0xffffc000) != 0) return(FALSE); } x.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); y.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); if (prcfxClip == (RECTFX*) NULL || bIntersect(&rcfxBound, prcfxClip)) { while (TRUE) { register LONG lTestMagnitude = TEST_MAGNITUDE_INITIAL << cShift; if (x.lError() <= lTestMagnitude && y.lError() <= lTestMagnitude) break; cShift += 2; x.vLazyHalveStepSize(cShift); y.vLazyHalveStepSize(cShift); cSteps <<= 1; } } x.vSteadyState(cShift); y.vSteadyState(cShift); // Note that this handles the case where the initial error for // the Bezier is already less than TEST_MAGNITUDE_NORMAL: x.vTakeStep(); y.vTakeStep(); cSteps--; return(TRUE); } BOOL BEZIER32::bNext(POINTFIX* pptfx) { // Return current point: pptfx->x = x.fxValue() + rcfxBound.xLeft; pptfx->y = y.fxValue() + rcfxBound.yTop; // If cSteps == 0, that was the end point in the curve! if (cSteps == 0) return(FALSE); // Okay, we have to step: if (MAX(x.lError(), y.lError()) > TEST_MAGNITUDE_NORMAL) { x.vHalveStepSize(); y.vHalveStepSize(); cSteps <<= 1; } while (!(cSteps & 1) && x.lParentErrorDividedBy4() <= (TEST_MAGNITUDE_NORMAL >> 2) && y.lParentErrorDividedBy4() <= (TEST_MAGNITUDE_NORMAL >> 2)) { x.vDoubleStepSize(); y.vDoubleStepSize(); cSteps >>= 1; } cSteps--; x.vTakeStep(); y.vTakeStep(); return(TRUE); } ///////////////////////////////////////////////////////////////////////////// // CSimpsonsView IMPLEMENT_DYNCREATE(CSimpsonsView, CView) BEGIN_MESSAGE_MAP(CSimpsonsView, CView) //{{AFX_MSG_MAP(CSimpsonsView) ON_WM_SIZE() ON_WM_LBUTTONUP() ON_WM_LBUTTONDOWN() ON_WM_KEYDOWN() ON_WM_RBUTTONDOWN() ON_WM_MOUSEMOVE() ON_WM_MOUSEWHEEL() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSimpsonsView construction/destruction CSimpsonsView::CSimpsonsView() : m_sizWin(0, 0) { m_pDD = NULL; m_pddsScreen = NULL; m_pSurfFactory = NULL; m_pDX2D = NULL; m_pDX2DScreen = NULL; m_pDX2DDebug = NULL; m_CycleTests = false; m_testCaseNumber = 0; m_bIgnoreStroke = m_bIgnoreFill = false; m_dwRenderTime = 0; m_gpPathArray = NULL; m_XForm.SetIdentity(); m_centerPoint.x = m_centerPoint.y = 0; m_lastPoint.x = m_lastPoint.y = 0; m_tracking = m_scaling = false; m_bLButton = false; } CSimpsonsView::~CSimpsonsView() { if (m_gpPathArray) delete [] m_gpPathArray; MMRELEASE(m_pDD); MMRELEASE(m_pddsScreen); MMRELEASE(m_pSurfFactory); MMRELEASE(m_pDX2D); MMRELEASE(m_pDX2DScreen); MMRELEASE(m_pDX2DDebug); CoUninitialize(); } BOOL CSimpsonsView::PreCreateWindow(CREATESTRUCT &cs) { HRESULT hr = S_OK; IDXTransformFactory *pTranFact = NULL; IDirectDrawFactory *pDDrawFact = NULL; IDirectDraw *pDD = NULL; if (CView::PreCreateWindow(cs) == false) return false; CHECK_HR(hr = CoInitialize(NULL)); //--- Create the transform factory CHECK_HR(hr = ::CoCreateInstance(CLSID_DXTransformFactory, NULL, CLSCTX_INPROC, IID_IDXTransformFactory, (void **) &pTranFact)); CHECK_HR(hr = ::CoCreateInstance(CLSID_DX2D, NULL, CLSCTX_INPROC, IID_IDX2D, (void **) &m_pDX2D)); CHECK_HR(hr = ::CoCreateInstance(CLSID_DX2D, NULL, CLSCTX_INPROC, IID_IDX2D, (void **) &m_pDX2DScreen)); /* m_pDX2D->QueryInterface(IID_IDX2DDebug, (void **) &m_pDX2DDebug);*/ CHECK_HR(hr = m_pDX2D->SetTransformFactory(pTranFact)); CHECK_HR(hr = m_pDX2DScreen->SetTransformFactory(pTranFact)); CHECK_HR(hr = pTranFact->QueryInterface(IID_IDXSurfaceFactory, (void **) &m_pSurfFactory)); //--- Create the direct draw object CHECK_HR(hr = ::CoCreateInstance(CLSID_DirectDrawFactory, NULL, CLSCTX_INPROC, IID_IDirectDrawFactory, (void **) &pDDrawFact)); CHECK_HR(hr = pDDrawFact->CreateDirectDraw( NULL, m_hWnd, DDSCL_NORMAL, 0, NULL, &pDD)); CHECK_HR(hr = pDD->QueryInterface( IID_IDirectDraw3, (void **) &m_pDD)); // Create the primary ddraw surface (m_pddsScreen) DDSURFACEDESC ddsd; ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; CHECK_HR(hr = m_pDD->CreateSurface(&ddsd, &m_pddsScreen, NULL)); CHECK_HR(hr = m_pDX2DScreen->SetSurface(m_pddsScreen)); e_Exit: MMRELEASE(pTranFact); MMRELEASE(pDDrawFact); MMRELEASE(pDD); return (hr == S_OK); } ///////////////////////////////////////////////////////////////////////////// // CSimpsonsView drawing void CSimpsonsView::OnSize(UINT nType, int cx, int cy) { // MMTRACE("OnSize\n"); m_centerPoint.x = cx / 2; m_centerPoint.y = cy / 2; CView::OnSize(nType, cx, cy); } HRESULT CSimpsonsView::Resize(DWORD nX, DWORD nY) { // MMTRACE("Resize\n"); HRESULT hr; IDirectDrawSurface *pdds = NULL; CDXDBnds Bnds; MMASSERT(nX && nY); // store the new size m_sizWin.cx = nX; m_sizWin.cy = nY; Bnds.SetXYSize(m_sizWin); CHECK_HR(hr = m_pSurfFactory->CreateSurface(m_pDD, NULL, &DDPF_PMARGB32, &Bnds, 0, NULL, IID_IDXSurface, (void **) &pdds)); CHECK_HR(hr = m_pDX2D->SetSurface(pdds)); // render the image to the backbuffer CHECK_HR(hr = Render(true)); // Hack: Get the client rect in screen coordinates. My hacky way of doing // this is to get the window rect and adjust it. GetWindowRect(&m_clientRectHack); m_clientRectHack.left += 2; m_clientRectHack.top += 2; m_clientRectHack.right -= 2; m_clientRectHack.bottom -= 2; CHECK_HR(hr = m_pDX2DScreen->SetClipRect(&m_clientRectHack)); e_Exit: MMRELEASE(pdds); return hr; } void CSimpsonsView::OnDraw(CDC *pDC) { // MMTRACE("OnDraw\n"); HRESULT hr; HDC hdcSurf = NULL; IDirectDrawSurface *pdds = NULL; DDSURFACEDESC ddsd; RECT rDim; UpdateStatusMessage(); // get the size of the invalid area GetClientRect(&rDim); if ((rDim.left == rDim.right) || (rDim.top == rDim.bottom)) return; CSimpsonsDoc *pDoc = GetDocument(); // if this is a new document, build the GDI+ path list if (pDoc->HasNeverRendered()) BuildGDIPList(); // check if the back buffer has changed size if (pDoc->HasNeverRendered() || (rDim.right != m_sizWin.cx) || (rDim.bottom != m_sizWin.cy)) { ResetTransform(); CHECK_HR(hr = Resize(rDim.right, rDim.bottom)); pDoc->MarkRendered(); } ddsd.dwSize = sizeof(ddsd); CHECK_HR(hr = m_pDX2D->GetSurface(IID_IDirectDrawSurface, (void **) &pdds)); CHECK_HR(hr = pdds->GetSurfaceDesc(&ddsd)); CHECK_HR(hr = pdds->GetDC(&hdcSurf)); ::BitBlt(pDC->m_hDC, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcSurf, 0, 0, SRCCOPY); e_Exit: if (hdcSurf) { pdds->ReleaseDC( hdcSurf ); } MMRELEASE(pdds); } ///////////////////////////////////////////////////////////////////////////// // CSimpsonsView diagnostics #ifdef _DEBUG void CSimpsonsView::AssertValid() const { CView::AssertValid(); } void CSimpsonsView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CSimpsonsDoc* CSimpsonsView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSimpsonsDoc))); return (CSimpsonsDoc*)m_pDocument; } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSimpsonsView message handlers #include "ddhelper.h" typedef DWORD FP; #define nEXPBIAS 127 #define nEXPSHIFTS 23 #define nEXPLSB (1 << nEXPSHIFTS) #define maskMANT (nEXPLSB - 1) #define FloatToFixedNoScale(nDst, fSrc) MACSTART \ float fTmp = fSrc; \ DWORD nRaw = *((FP *) &(fTmp)); \ if (nRaw < (nEXPBIAS << nEXPSHIFTS)) \ nDst = 0; \ else \ nDst = ((nRaw | nEXPLSB) << 8) >> ((nEXPBIAS + 31) - (nRaw >> nEXPSHIFTS)); \ MACEND // This routine converts a 'float' to 28.4 fixed point format. inline FIX FloatToFix(float f) { FIX i; FloatToFixedNoScale(i, f*FIX_SCALE); return(i); } /* Draw a polygon with GDI. This version flattens beziers and packages the polygon up into a single polypoly call. (Compare to DrawGDIPolyPathAPI) */ void CSimpsonsView::DrawGDIPoly(HDC hDC, PolyInfo *pPoly) { POINT rgpt[1024]; DWORD rgcpt[30]; DWORD cPoints = pPoly->cPoints; BEZIER32 bez; POINTFIX aptfxBez[4]; MMASSERT(cPoints); DWORD *pcptBuffer; POINT *pptBuffer; POINT *pptFigure; DXFPOINT *pCurPoint = pPoly->pPoints; DXFPOINT *pCurPointLimit = pPoly->pPoints + cPoints; BYTE *pCurCode = pPoly->pCodes; pptBuffer = rgpt; pptFigure = rgpt; pcptBuffer = rgcpt; // In an effort to reduce our per-call overhead, we try to avoid // calling GDI's BeginPath/EndPath/FillPath routines, because they // just add significant time when the drawing is small. Instead, // we package everything up into PolyPoly calls that will draw // immediately. while (TRUE) { if (*pCurCode == PT_BEZIERTO) { aptfxBez[0].x = FloatToFix((pCurPoint-1)->x); aptfxBez[0].y = FloatToFix((pCurPoint-1)->y); aptfxBez[1].x = FloatToFix((pCurPoint)->x); aptfxBez[1].y = FloatToFix((pCurPoint)->y); aptfxBez[2].x = FloatToFix((pCurPoint+1)->x); aptfxBez[2].y = FloatToFix((pCurPoint+1)->y); aptfxBez[3].x = FloatToFix((pCurPoint+2)->x); aptfxBez[3].y = FloatToFix((pCurPoint+2)->y); if (bez.bInit(aptfxBez, NULL)) { while (bez.bNext(pptBuffer++)) ; } pCurPoint += 3; pCurCode += 3; } else { pptBuffer->x = FloatToFix(pCurPoint->x); pptBuffer->y = FloatToFix(pCurPoint->y); pptBuffer++; pCurPoint++; pCurCode++; } if (pCurPoint == pCurPointLimit) { *pcptBuffer++ = (DWORD)(pptBuffer - pptFigure); break; } if (*pCurCode == PT_MOVETO) { *pcptBuffer++ = (DWORD)(pptBuffer - pptFigure); pptFigure = pptBuffer; } } if (pPoly->dwFlags & DX2D_FILL) { if (!m_bNullPenSelected) { SelectObject(hDC, m_hNullPen); m_bNullPenSelected = TRUE; } PolyPolygon(hDC, rgpt, (INT*) rgcpt, (int) (pcptBuffer - rgcpt)); } else { if (m_bNullPenSelected) { SelectObject(hDC, m_hStrokePen); m_bNullPenSelected = FALSE; } PolyPolyline(hDC, rgpt, rgcpt, (DWORD) (pcptBuffer - rgcpt)); } } /* Same as DrawGDIPoly, but uses the slow GDI path functions. */ void CSimpsonsView::DrawGDIPolyPathAPI(HDC hDC, PolyInfo *pPoly) { POINTFIX aptfxBez[3]; POINTFIX pt; DXFPOINT *pCurPoint = pPoly->pPoints; DXFPOINT *pCurPointLimit = pPoly->pPoints + pPoly->cPoints; BYTE *pCurCode = pPoly->pCodes; BeginPath(hDC); while (pCurPoint < pCurPointLimit) { switch (*pCurCode) { case PT_BEZIERTO: aptfxBez[0].x = FloatToFix((pCurPoint)->x); aptfxBez[0].y = FloatToFix((pCurPoint)->y); aptfxBez[1].x = FloatToFix((pCurPoint+1)->x); aptfxBez[1].y = FloatToFix((pCurPoint+1)->y); aptfxBez[2].x = FloatToFix((pCurPoint+2)->x); aptfxBez[2].y = FloatToFix((pCurPoint+2)->y); PolyBezierTo(hDC, aptfxBez, 3); pCurPoint += 3; pCurCode += 3; break; case PT_LINETO: pt.x = FloatToFix(pCurPoint->x); pt.y = FloatToFix(pCurPoint->y); PolylineTo(hDC, &pt, 1); pCurPoint++; pCurCode++; break; case PT_MOVETO: MoveToEx(hDC, FloatToFix(pCurPoint->x), FloatToFix(pCurPoint->y), NULL); pCurPoint++; pCurCode++; break; } } EndPath(hDC); if (pPoly->dwFlags & DX2D_FILL) { if (!m_bNullPenSelected) { SelectObject(hDC, m_hNullPen); m_bNullPenSelected = TRUE; } FillPath(hDC); } else { if (m_bNullPenSelected) { SelectObject(hDC, m_hStrokePen); m_bNullPenSelected = FALSE; } StrokePath(hDC); } } /* Draw the scene using GDI. */ void CSimpsonsView::DrawAllGDI(HDC hDC) { DWORD nStart, nEnd; int dataSource = ThisTestCase[TC::At_Source]; nStart = timeGetTime(); HPEN hpenOld; HBRUSH hbrushOld; HGDIOBJ hBrush; HDC hdcOutput; const RenderCmd *pCurCmd = GetDocument()->GetRenderCommands(); if (pCurCmd == NULL) return; PolyInfo *pPoly; BrushInfo *pBrush; PenInfo *pPen; m_hNullPen = (HPEN) GetStockObject(NULL_PEN); m_bNullPenSelected = TRUE; if (ThisTestCase[TC::At_Destination]==TC::ToMetafile) { // Determine the picture frame dimensions. // iWidthMM is the display width in millimeters. // iHeightMM is the display height in millimeters. // iWidthPels is the display width in pixels. // iHeightPels is the display height in pixels LONG iWidthMM = GetDeviceCaps(hDC, HORZSIZE); LONG iHeightMM = GetDeviceCaps(hDC, VERTSIZE); LONG iWidthPels = GetDeviceCaps(hDC, HORZRES); LONG iHeightPels = GetDeviceCaps(hDC, VERTRES); // Hack the client rect RECT rect={0, 0, 500, 500}; // Convert client coordinates to .01-mm units. // Use iWidthMM, iWidthPels, iHeightMM, and // iHeightPels to determine the number of // .01-millimeter units per pixel in the x- // and y-directions. rect.left = (rect.left * iWidthMM * 100)/iWidthPels; rect.top = (rect.top * iHeightMM * 100)/iHeightPels; rect.right = (rect.right * iWidthMM * 100)/iWidthPels; rect.bottom = (rect.bottom * iHeightMM * 100)/iHeightPels; hdcOutput = CreateEnhMetaFile(hDC, "simpgdi.emf", &rect, NULL); if (!hdcOutput) { return; } } else { hdcOutput = hDC; } if (dataSource==TC::FromMetafile) { HENHMETAFILE hemf = GetEnhMetaFile("simpgdi.emf"); if (hemf) { RECT rect = {0, 0, 500, 500}; PlayEnhMetaFile(hdcOutput, hemf, &rect); DeleteEnhMetaFile(hemf); } else { printf("Metafile didn't load!\n"); } } else { HGDIOBJ hOldBrush = SelectObject(hdcOutput, GetStockObject(WHITE_BRUSH)); HGDIOBJ hOldPen = SelectObject(hdcOutput, m_hNullPen); // Here we set a 1/16th shrinking transform. We will have to // scale up all the points we give GDI by a factor of 16. // // We do this because when set in advanced mode, NT's GDI can // rasterize with 28.4 precision, and since we have factional // coordinates, this will make the result look better on NT. // // (There will be no difference on Win9x.) SetGraphicsMode(hdcOutput, GM_ADVANCED); SetMapMode(hdcOutput, MM_ANISOTROPIC); SetWindowExtEx(hdcOutput, FIX_SCALE, FIX_SCALE, NULL); for (;pCurCmd->nType != typeSTOP; pCurCmd++) { switch (pCurCmd->nType) { case typePOLY: // draw the polygon pPoly = (PolyInfo *) pCurCmd->pvData; if (!((m_bIgnoreStroke && (pPoly->dwFlags & DX2D_STROKE)) || (m_bIgnoreFill && (pPoly->dwFlags & DX2D_FILL)))) { if (dataSource == TC::PathAPI) { DrawGDIPolyPathAPI(hdcOutput, (PolyInfo *) pCurCmd->pvData); } else { ASSERT(dataSource == TC::CreatePoly); DrawGDIPoly(hdcOutput, (PolyInfo *) pCurCmd->pvData); } } break; case typeBRUSH: // select a new brush { pBrush = (BrushInfo *) pCurCmd->pvData; DWORD dwColor = pBrush->Color; BYTE r = BYTE(dwColor >> 16); BYTE g = BYTE(dwColor >> 8); BYTE b = BYTE(dwColor); hBrush = CreateSolidBrush(RGB(r,g,b)); hbrushOld = (HBRUSH) SelectObject(hdcOutput, hBrush); DeleteObject(hbrushOld); } break; case typePEN: // select a new pen { pPen = (PenInfo *) pCurCmd->pvData; DWORD dwColor = pPen->Color; BYTE r = BYTE(dwColor >> 16); BYTE g = BYTE(dwColor >> 8); BYTE b = BYTE(dwColor); hpenOld = m_hStrokePen; m_hStrokePen = CreatePen(PS_SOLID, DWORD(pPen->fWidth * FIX_SCALE), RGB(r, g, b)); if (!m_bNullPenSelected) { SelectObject(hdcOutput, m_hStrokePen); } DeleteObject(hpenOld); } break; } } SetMapMode(hdcOutput, MM_TEXT); SetGraphicsMode(hdcOutput, GM_COMPATIBLE); hbrushOld = (HBRUSH) SelectObject(hdcOutput, hOldBrush); hpenOld = (HPEN) SelectObject(hdcOutput, hOldPen); DeleteObject(hbrushOld); DeleteObject(hpenOld); DeleteObject(m_hStrokePen); } nEnd = timeGetTime(); m_dwRenderTime = nEnd-nStart; if (ThisTestCase[TC::At_Destination]==TC::ToMetafile) { DeleteEnhMetaFile(CloseEnhMetaFile(hdcOutput)); } } void CSimpsonsView::DrawGDIPPoly(Graphics *g, PolyInfo *pPoly, Pen *pen, Brush *brush) { GraphicsPath path(FillModeAlternate); DXFPOINT *pCurPoint = pPoly->pPoints; DXFPOINT *pCurPointLimit = pPoly->pPoints + pPoly->cPoints; BYTE *pCurCode = pPoly->pCodes; DXFPOINT currentPosition; while (pCurPoint < pCurPointLimit) { switch (*pCurCode) { case PT_BEZIERTO: path.AddBezier( (pCurPoint-1)->x, (pCurPoint-1)->y, (pCurPoint) ->x, (pCurPoint) ->y, (pCurPoint+1)->x, (pCurPoint+1)->y, (pCurPoint+2)->x, (pCurPoint+2)->y); pCurPoint += 3; pCurCode += 3; break; case PT_MOVETO: path.StartFigure(); pCurPoint++; pCurCode++; break; case PT_LINETO: path.AddLine( (pCurPoint-1)->x, (pCurPoint-1)->y, (pCurPoint)->x, (pCurPoint)->y); pCurPoint++; pCurCode++; break; } } if (pPoly->dwFlags & DX2D_FILL) { g->FillPath(brush, &path); } else { g->DrawPath(pen, &path); } } struct BitmapInfo { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[256]; // 256 is the maximum palette size }; /* Build an array of GDI+ paths for the current document. This is used as a 'native' data source - so that we don't time path creation when rendering. Even in this mode, DrawAllGDIP() still uses the RenderCmd buffer to read pen and brush data. */ void CSimpsonsView::BuildGDIPList() { // Free the old path array, if any if (m_gpPathArray) { delete [] m_gpPathArray; m_gpPathArray = NULL; } const RenderCmd *pCmd = GetDocument()->GetRenderCommands(); const RenderCmd *pCurCmd; if (!pCmd) return; // Count the number of polygons int count=0; for (pCurCmd=pCmd; pCurCmd->nType != typeSTOP; pCurCmd++) { if (pCurCmd->nType == typePOLY) count++; } m_gpPathArray = new GraphicsPath [count]; if (!m_gpPathArray) return; GraphicsPath *pPath=m_gpPathArray; PolyInfo *pPoly; // Add each polygon to the path array for (pCurCmd=pCmd; pCurCmd->nType != typeSTOP; pCurCmd++) { if (pCurCmd->nType==typePOLY) { pPoly = (PolyInfo *) pCurCmd->pvData; DXFPOINT *pCurPoint = pPoly->pPoints; DXFPOINT *pCurPointLimit = pPoly->pPoints + pPoly->cPoints; BYTE *pCurCode = pPoly->pCodes; DXFPOINT currentPosition; while (pCurPoint < pCurPointLimit) { switch (*pCurCode) { case PT_BEZIERTO: pPath->AddBezier( (pCurPoint-1)->x, (pCurPoint-1)->y, (pCurPoint) ->x, (pCurPoint) ->y, (pCurPoint+1)->x, (pCurPoint+1)->y, (pCurPoint+2)->x, (pCurPoint+2)->y); pCurPoint += 3; pCurCode += 3; break; case PT_MOVETO: pPath->StartFigure(); pCurPoint++; pCurCode++; break; case PT_LINETO: pPath->AddLine( (pCurPoint-1)->x, (pCurPoint-1)->y, (pCurPoint)->x, (pCurPoint)->y); pCurPoint++; pCurCode++; break; } } pPath++; } } printf ("BuildGDIPList successful\n"); } void CSimpsonsView::DrawAllGDIP(HDC hDC) { DWORD nStart, nEnd; // // START TIMING // nStart = timeGetTime(); int dataSource = ThisTestCase[TC::At_Source]; const RenderCmd *pCurCmd = GetDocument()->GetRenderCommands(); if (pCurCmd == NULL) return; PolyInfo *pPoly; BrushInfo *pBrush; PenInfo *pPen; GraphicsPath *pPath = NULL; if (dataSource==TC::Native) { pPath = m_gpPathArray; if (!pPath) { printf("GDI+ Native data is invalid\n"); return; } } Graphics *gOutput, *g; Metafile *recMetafile, *playMetafile; g = Graphics::FromHDC(hDC); if (ThisTestCase[TC::At_Destination]==TC::ToMetafile) { recMetafile = new Metafile(L"simpsons.emf", hDC); if (!recMetafile) { delete g; return; } gOutput = Graphics::FromImage(recMetafile); } else { gOutput = g; } if (ThisTestCase[TC::At_Aliasing]==TC::Antialiased) { gOutput->SetSmoothingMode(SmoothingModeAntiAlias); } else { gOutput->SetSmoothingMode(SmoothingModeNone); } if (dataSource==TC::FromMetafile) { playMetafile = new Metafile(L"simpsons.emf"); if (playMetafile) { GpRectF playbackRect; gOutput->GetVisibleClipBounds(&playbackRect); gOutput->DrawImage(playMetafile, 0, 0); } else { printf("Metafile didn't load!\n"); } } else { Color black(0,0,0); Pen currentPen(black, 1); SolidBrush currentBrush(black); for (;pCurCmd->nType != typeSTOP; pCurCmd++) { switch (pCurCmd->nType) { case typePOLY: // convert points to fixed point pPoly = (PolyInfo *) pCurCmd->pvData; if (!((m_bIgnoreStroke && (pPoly->dwFlags & DX2D_STROKE)) || (m_bIgnoreFill && (pPoly->dwFlags & DX2D_FILL)))) { if (pPath) { // Draw from the pre-created path list if (pPoly->dwFlags & DX2D_FILL) { gOutput->FillPath(¤tBrush, pPath); } else { gOutput->DrawPath(¤tPen, pPath); } } else { ASSERT(dataSource == TC::PathAPI); // Create the path and draw it DrawGDIPPoly(gOutput, (PolyInfo *) pCurCmd->pvData, ¤tPen, ¤tBrush); } } if(pPath != NULL) pPath++; break; case typeBRUSH: { // change brush color pBrush = (BrushInfo *) pCurCmd->pvData; DWORD dwColor = pBrush->Color; BYTE r = BYTE(dwColor >> 16); BYTE g = BYTE(dwColor >> 8); BYTE b = BYTE(dwColor); Color c(r,g,b); currentBrush.SetColor(c); } break; case typePEN: #if 0 { // select a new pen pPen = (PenInfo *) pCurCmd->pvData; DWORD dwColor = pPen->Color; BYTE r = BYTE(dwColor >> 16); BYTE g = BYTE(dwColor >> 8); BYTE b = BYTE(dwColor); currentPen.SetPenColor(Color(r,g,b)); } #endif break; } } } gOutput->Flush(); if (ThisTestCase[TC::At_Source]==TC::FromMetafile) { delete playMetafile; } if (ThisTestCase[TC::At_Destination]==TC::ToMetafile) { delete gOutput; delete recMetafile; } delete g; // // STOP TIMING // nEnd = timeGetTime(); m_dwRenderTime = nEnd-nStart; } void CSimpsonsView::UpdateStatusMessage() { using namespace TC; sprintf(g_rgchTmpBuf, "Time: %dms %s Src: %s Dst: %s, %s", // GetDocument()->GetFileName(), m_dwRenderTime, OptionStr[At_Library][ThisTestCase[At_Library]], OptionStr[At_Source][ThisTestCase[At_Source]], OptionStr[At_Destination][ThisTestCase[At_Destination]], OptionStr[At_Aliasing][ThisTestCase[At_Aliasing]] ); // OutputDebugString(g_rgchTmpBuf); // OutputDebugString("\n"); CFrameWnd *pFrame = GetParentFrame(); if (pFrame) pFrame->SetMessageText(g_rgchTmpBuf); } void CSimpsonsView::DrawAll(IDX2D *pDX2D) { DWORD nStart, nEnd; nStart = timeGetTime(); const RenderCmd *pCurCmd = GetDocument()->GetRenderCommands(); DXBRUSH Brush; DXPEN Pen; // intialize Pen and Brush Pen.pTexture = NULL; Pen.TexturePos.x = 0.f; Pen.TexturePos.y = 0.f; Brush.pTexture = NULL; Brush.TexturePos.x = 0.f; Brush.TexturePos.y = 0.f; PolyInfo *pPoly; BrushInfo *pBrush; PenInfo *pPen; bool bBrush = false, bPen = false; for (;pCurCmd->nType != typeSTOP; pCurCmd++) { switch (pCurCmd->nType) { case typePOLY: pPoly = (PolyInfo *) pCurCmd->pvData; if (!((m_bIgnoreStroke && (pPoly->dwFlags & DX2D_STROKE)) || (m_bIgnoreFill && (pPoly->dwFlags & DX2D_FILL)))) { pDX2D->AAPolyDraw(pPoly->pPoints, pPoly->pCodes, pPoly->cPoints, 4, pPoly->dwFlags); } break; case typeBRUSH: // select a new brush pBrush = (BrushInfo *) pCurCmd->pvData; Brush.Color = pBrush->Color; pDX2D->SetBrush(&Brush); bBrush = true; break; case typePEN: // select a new pen pPen = (PenInfo *) pCurCmd->pvData; Pen.Color = pPen->Color; Pen.Width = pPen->fWidth; Pen.Style = pPen->dwStyle; pDX2D->SetPen(&Pen); bPen = true; break; } } nEnd = timeGetTime(); m_dwRenderTime = nEnd-nStart; } HRESULT CSimpsonsView::Render(bool bInvalidate) { // MMTRACE("Render\n"); RECT rc = {0, 0, 500, 400}; HRESULT hr = S_OK; IDirectDrawSurface *pdds = NULL; IDXSurface *pDXSurf = NULL; HDC screenDC = NULL, drawDC = NULL, memDC = NULL; BOOL bFinished = false; DWORD executionTime; sprintf(g_rgchTmpBuf, "Rendering with %s...", TC::OptionStr[TC::At_Library][ThisTestCase[TC::At_Library]]); CFrameWnd *pFrame = GetParentFrame(); if (pFrame) { pFrame->SetMessageText(g_rgchTmpBuf); } CHECK_HR(hr = m_pDX2D->GetSurface(IID_IDXSurface, (void **) &pDXSurf)); CHECK_HR(hr = pDXSurf->GetDirectDrawSurface(IID_IDirectDrawSurface, (void **) &pdds)); while (!bFinished) { DXFillSurface(pDXSurf, g_aColors[g_ulColorIndex]); //--- Set alias mode // CHECK_HR(hr = m_pDX2D->_SetDelegateToGDI(m_bAliased)); //--- Set global opacity // CHECK_HR(hr = m_pDX2D->SetGlobalOpacity(1.f)); CHECK_HR(hr = m_pDX2D->SetWorldTransform(&m_XForm)); CDX2DXForm xform; xform = m_XForm; xform.Translate((REAL)m_clientRectHack.left, (REAL)m_clientRectHack.top); CHECK_HR(hr = m_pDX2DScreen->SetWorldTransform(&xform)); //--- Get the DC of the DD surface. CHECK_HR(hr = pdds->GetDC(&memDC)); // render the scene and compute timing // Set the timer resolution to 1ms if (timeBeginPeriod(1)==TIMERR_NOCANDO) { hr = ERROR_INVALID_FUNCTION; goto e_Exit; } /* For the direct-to-screen cases, we bypass the ddraw surface. For repaints to look pretty, though, we copy the result to the ddraw surface (after the timer has been turned off.) */ drawDC = memDC; HBRUSH backgroundBrush; if (ThisTestCase[TC::At_Destination]==TC::Screen) { screenDC = ::GetDC(m_hWnd); backgroundBrush = CreateSolidBrush(g_aColors[g_ulColorIndex] & 0xffffff); FillRect(screenDC, &rc, backgroundBrush); DeleteObject(backgroundBrush); drawDC = screenDC; } // The 'DrawAll' routine will actually do the timeGetTime() and store the // result in m_dwRenderTime. switch (ThisTestCase[TC::At_Library]) { case TC::GDI: DrawAllGDI(drawDC); break; case TC::Meta: if (ThisTestCase[TC::At_Destination]==TC::Screen) { DrawAll(m_pDX2DScreen); } else { DrawAll(m_pDX2D); } break; case TC::GDIP: DrawAllGDIP(drawDC); break; } // !!! Release and re-acquire the DDraw surface DC to work-around // a current limitation in GDI+ where Graphics(hdc) nukes the // hdc of a DDraw surface pdds->ReleaseDC(memDC); memDC = NULL; CHECK_HR(hr = pdds->GetDC(&memDC)); if (ThisTestCase[TC::At_Destination]==TC::Screen) { bInvalidate = false; UpdateStatusMessage(); // Copy to from the screen to the ddraw surface, // so that repaints work. ::BitBlt(memDC, 0, 0, 500, 400, screenDC, 0, 0, SRCCOPY); } timeEndPeriod(1); // Reset the multimedia timer to default resolution pdds->ReleaseDC(memDC); memDC = NULL; if (screenDC) { ::ReleaseDC(m_hWnd, screenDC); screenDC = NULL; } if (m_CycleTests) { bFinished = IncrementTest(); } else { bFinished = true; } } e_Exit: //--- Clean-up if (pdds) { if (memDC) pdds->ReleaseDC(memDC); MMRELEASE(pdds); } if (screenDC) { ::ReleaseDC(m_hWnd, screenDC); } MMRELEASE(pDXSurf); //--- draw if (bInvalidate) Invalidate(); return hr; } void CSimpsonsView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { bool bNothing = false; float fTheta = 1.f; if (nChar == 'G') { ToggleGDI(); } else if (nChar == 'A') { bNothing = !IncrementAttribute(TC::At_Aliasing); } else if (nChar == 'D') { bNothing = !IncrementAttribute(TC::At_Destination); } else if (nChar == 'I') { IncrementTest(); } else if ((nChar >= '0') && (nChar <= '9')) { g_ulColorIndex = (nChar - '0'); } else if (nChar == ' ') { // Redraw } else if (nChar == 'R') { ResetTransform(); } else if (nChar == 'F') { ToggleFill(); } else if (nChar == 'S') { ToggleStroke(); } else if (nChar == 'C') { m_CycleTests = true; m_testCaseNumber = 0; if (m_pDX2DDebug) m_pDX2DDebug->SetDC(NULL); } else if (nChar == VK_LEFT) { AddRotation(fTheta); } else if (nChar == VK_RIGHT) { AddRotation(-fTheta); } else { bNothing = true; } if (!bNothing) Render(true); if (m_CycleTests) { PrintTestResults(); m_CycleTests = false; } CView::OnKeyDown(nChar, nRepCnt, nFlags); } void CSimpsonsView::ToggleStroke() { m_bIgnoreStroke ^= 1; } void CSimpsonsView::ToggleFill() { m_bIgnoreFill ^= 1; } void CSimpsonsView::ResetTransform() { m_XForm.SetIdentity(); } void CSimpsonsView::AddRotation(float fTheta) { m_XForm.Rotate(fTheta); } void CSimpsonsView::ToggleGDI() { HRESULT hr = S_OK; IDXSurface *pdxsRender = NULL; IDirectDrawSurface *pddsRender = NULL; HDC hDC = NULL; IncrementAttribute(TC::At_Library); if (m_pDX2DDebug) { switch (ThisTestCase[TC::At_Library]) { case TC::GDI: case TC::GDIP: CHECK_HR(hr = m_pDX2D->GetSurface(IID_IDXSurface, (void**) &pdxsRender)); CHECK_HR(hr = pdxsRender->QueryInterface(IID_IDirectDrawSurface, (void **) &pddsRender)); CHECK_HR(hr = pddsRender->GetDC(&hDC)); m_pDX2DDebug->SetDC(hDC); break; case TC::Meta: m_pDX2DDebug->SetDC(NULL); break; } } e_Exit: if (pddsRender && hDC) pddsRender->ReleaseDC(hDC); MMRELEASE(pddsRender); MMRELEASE(pdxsRender); } void CSimpsonsView::OnLButtonDown(UINT nFlags, CPoint pt) { CView::OnLButtonDown(nFlags, pt); } void CSimpsonsView::OnRButtonDown(UINT nFlags, CPoint point) { CView::OnRButtonDown(nFlags, point); } void CSimpsonsView::ForceUpdate() { HRESULT hr; Render(false); HDC hdcSurf = NULL; IDirectDrawSurface *pdds = NULL; DDSURFACEDESC ddsd; CDC *pDC = GetDC(); ddsd.dwSize = sizeof(ddsd); CHECK_HR(hr = m_pDX2D->GetSurface(IID_IDirectDrawSurface, (void **) &pdds)); CHECK_HR(hr = pdds->GetSurfaceDesc(&ddsd)); CHECK_HR(hr = pdds->GetDC(&hdcSurf)); ::BitBlt(pDC->m_hDC, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcSurf, 0, 0, SRCCOPY); UpdateStatusMessage(); e_Exit: if (pdds && hdcSurf) pdds->ReleaseDC(hdcSurf); MMRELEASE(pdds); } void CSimpsonsView::DoMove(POINT &pt) { if ((m_lastPoint.x != pt.x) && (m_lastPoint.y != pt.y)) { float dx = float(pt.x - m_lastPoint.x); float dy = float(pt.y - m_lastPoint.y); if (m_scaling) { float scale = 1.f + dx * fZOOMFACTOR; CLAMP(scale, fSCALEMIN, fSCALEMAX); m_XForm.Translate(float(-m_centerPoint.x), float(-m_centerPoint.y)); m_XForm.Scale(scale, scale); m_XForm.Translate(float(m_centerPoint.x), float(m_centerPoint.y)); } else { // panning m_XForm.Translate(dx, dy); } ForceUpdate(); m_lastPoint = pt; } } void CSimpsonsView::OnLButtonUp(UINT nFlags, CPoint ptPassed) { POINT pt; GetCursorPos(&pt); ScreenToClient(&pt); DoMove(pt); CView::OnLButtonUp(nFlags, pt); } void CSimpsonsView::OnMouseMove(UINT nFlagsPassed, CPoint ptPassed) { // get current mouse position POINT pt; GetCursorPos(&pt); ScreenToClient(&pt); // check if left mouse button is down m_tracking = (GetAsyncKeyState(VK_LBUTTON) && (m_bLButton || IsInside(pt.x, pt.y, m_sizWin))); if (m_tracking) { if (m_bLButton) { DoMove(pt); } else { m_scaling = ((GetAsyncKeyState(VK_CONTROL) & ~0x1) != 0); m_bLButton = true; m_centerPoint = pt; m_lastPoint = pt; } } m_bLButton = m_tracking; CView::OnMouseMove(nFlagsPassed, ptPassed); } BOOL CSimpsonsView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { float fDelta = float (zDelta / 1200.f); float fScale = 1.f - fDelta; CLAMP(fScale, fSCALEMIN, fSCALEMAX); m_XForm.Translate(float(-m_centerPoint.x), float(-m_centerPoint.y)); m_XForm.Scale(fScale, fScale); m_XForm.Translate(float(m_centerPoint.x), float(m_centerPoint.y)); ForceUpdate(); return CView::OnMouseWheel(nFlags, zDelta, pt); } _cdecl main(INT argc, PCHAR argb[]) { return(1); }