/**************************************************************************\ * * Copyright (c) 1998-1999 Microsoft Corporation * * Abstract: * * Handle all the permutations of the creation and deletion of the * GpGraphics class. * * Revision History: * * 12/03/1998 andrewgo * Created it. * \**************************************************************************/ #include "precomp.hpp" #include "..\Render\HtTables.hpp" #include "printer.hpp" #include "winspool.h" #include "winbase.h" /**************************************************************************\ * * Function Description: * * Updates the draw bounds of the graphics. Resets the clipping. * * Arguments: * * [IN] x, y, width, height - Specifies the client drawable boundaries * * History: * * 03/30/2000 agodfrey * Created it. * \**************************************************************************/ VOID GpGraphics::UpdateDrawBounds( INT x, INT y, INT width, INT height ) { DpContext *context = Context; // Set up the surface bounds and the clip regions: SurfaceBounds.X = x; SurfaceBounds.Y = y; SurfaceBounds.Width = width; SurfaceBounds.Height = height; WindowClip.Set(x, y, width, height); context->VisibleClip.Set(x, y, width, height); // ContainerClip always contains the clipping for the container, // intersected with the WindowClip. Currently, the container is // infinite, so just set it to the WindowClip. context->ContainerClip.Set(x, y, width, height); context->AppClip.SetInfinite(); } /**************************************************************************\ * * Function Description: * * Resets the graphics state to its defaults. * * Arguments: * * [IN] x, y, width, height - Specifies the client drawable boundaries * * History: * * 12/04/1998 andrewgo * Created it. * \**************************************************************************/ VOID GpGraphics::ResetState( INT x, INT y, INT width, INT height ) { DpContext *context = Context; context->CompositingMode = CompositingModeSourceOver; context->CompositingQuality = CompositingQualityDefault; context->AntiAliasMode = FALSE; context->TextRenderHint = TextRenderingHintSystemDefault; context->TextContrast = DEFAULT_TEXT_CONTRAST; context->FilterType = InterpolationModeDefaultInternal; context->PixelOffset = PixelOffsetModeDefault; context->InverseOk = FALSE; context->WorldToPage.Reset(); context->ContainerToDevice.Reset(); this->SetPageTransform(UnitDisplay, 1.0f); // updates the matrix UpdateDrawBounds(x, y, width, height); } /**************************************************************************\ * * Function Description: * * Get the drawing bounds of a DC. Only intended for use by * GpGraphics::GpGraphics(HWND, HDC). * * Arguments: * * [IN] hdc - Specifies the DC * [OUT] rect - The returned client rectangle. * * Return Value: * * Status code * * Notes: * * See bug #93012. We used to just call GetClipBox, convert to device * coordinates, then boost the rectangle by one pixel on each side to cover * rounding error. But this was causing AV's - we really do need the exact * client rectangle. * * But we need good perf in common cases. So we do a two-step process * - check if the transform is such that there won't be rounding error * (and simply use GetClipBox if so). * Otherwise, save the DC, reset the transform, and then query. * * I tried an alternative - calling LPtoDP on 3 points to infer the transform * (as we do in InheritAppClippingAndTransform). * But because of rounding, and made worse by * NT bug #133322 (in the old NT RAID), it's nearly impossible * to infer the transform unambiguously. The particularly bad case is * when the world-to-device transform is a shrink, but the scale factor * is very close to 1. We'd decide it was a one-to-one transform, but it * would be susceptible to bug #133322. * * So, to round off this novel: We're using a much simpler approach, * which restricts the cases in which we can use the fast method, but * should be ok. * * Notes: * * This should really be a member of DpContext (bug #98174). * * History: * * 03/28/2000 agodfrey * Created it. * \**************************************************************************/ #if 0 // not used GpStatus GpGraphics::GetDCDrawBounds( HDC hdc, RECT *rect ) { BOOL hackResetClipping = FALSE; // Check if the transform is translation-only. If it is, we can avoid the // expense of cleaning the DC. GetGraphicsMode and GetMapMode are both // handled in user mode on NT. if ( (GetGraphicsMode(hdc) != GM_COMPATIBLE) || (GetMapMode(hdc) != MM_TEXT)) { // Clean the DC, to set the transform back to translation-only. ASSERT(Context->SaveDc == 0); Context->SaveDc = ::SaveDC(hdc); if (!Context->SaveDc) { return GenericError; } // CleanTheHdc shouldn't be resetting the clipping, but it does, // which messes up GetClipBox below. // So until bug #99338 is resolved, we must work around it. hackResetClipping = TRUE; Context->CleanTheHdc(hdc, FALSE); } // The code above is necessary because GetClipBox returns // logical coordinates, but we want device coordinates. // By this point, we've made sure that the transform is translation-only. if (GetClipBox(hdc, rect) == ERROR) { return GenericError; } // See bug #99338. We must reset the clipping, because that's what // CleanTheHdc normally does, and apparently some of our code relies on it. // If #99338 is resolved as suggested, this should go away. if (hackResetClipping) { SelectClipRgn(hdc, NULL); } #if DBG // Save the world-coordinate rectangle. RECT checkRect = *rect; #endif // Convert to device coordinates. if (!LPtoDP(hdc, reinterpret_cast(rect), 2)) { return GenericError; } // NT can sometimes return poorly-ordered rectangles, // but I don't think this applies to translation-only tranforms. ASSERT( (rect->left <= rect->right) && (rect->top <= rect->bottom)); // Verify that the transform was translation-only. // Note that this sanity check could fail to catch some transforms // which are 'almost' translation-only. But ask me if I care. ASSERT( ( (rect->right - rect->left) == (checkRect.right - checkRect.left)) && ( (rect->bottom - rect->top) == (checkRect.bottom - checkRect.top))); return Ok; } #endif /**************************************************************************\ * * Function Description: * * Create a GpGraphics class from a window handle. * * The advantage of this call over that of GetFromHdc is that * it can avoid the (slow) process of cleaning the DC. * * NOTE: This does not provide BeginPaint/EndPaint functionality, so * the app will still have to call BeginPaint/EndPaint in its * WM_PAINT call. * * Arguments: * * [IN] hwnd - Specifies the window * * Return Value: * * NULL if failure (such as with an invalid hwnd). * * History: * * 12/04/1998 andrewgo * Created it. * \**************************************************************************/ GpGraphics::GpGraphics( HWND hwnd, HDC hdc, INT clientWidth, INT clientHeight, HdcIcmMode icmMode, BOOL gdiLayered ) : BottomContext((hwnd != NULL) || (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASDISPLAY)) { ASSERT((hdc != NULL) || (hwnd != NULL)); //User doesn't have any protection against negative client areas so we //should consider them to be valid although empty. clientWidth = max(clientWidth, 0); clientHeight = max(clientHeight, 0); SetValid(TRUE); Context = &BottomContext; Type = GraphicsScreen; Metafile = NULL; DownLevel = FALSE; Printer = FALSE; LockedByGetDC = 0; Driver = Globals::DesktopDriver; Surface = Globals::DesktopSurface; Device = Globals::DesktopDevice; GdipBitmap = NULL; CreatedDevice = FALSE; // We don't do GetDC(hwnd) here and store that here because, // among other things, we don't want to hit the cached DC // limit on Win9x: Context->Hdc = hdc; Context->Hwnd = hwnd; Context->IcmMode = icmMode; Context->GdiLayered = gdiLayered; HDC tempHdc = (hdc != NULL) ? hdc : Globals::DesktopDevice->DeviceHdc; if (GetDeviceCaps(tempHdc, BITSPIXEL) <= 8) { Context->PaletteMap = new EpPaletteMap(tempHdc); if (!Context->PaletteMap || !Context->PaletteMap->IsValid()) { WARNING(("Unable to compute palette translation vector")); SetValid(FALSE); return; } Context->PaletteMap->SetUniqueness(Globals::PaletteChangeCount); } ResetState(0, 0, clientWidth, clientHeight); // Now inherit state from the HDC: if (hwnd == NULL) { // In addition to extracting the HDC's transform state, this // will also extract app-specified clipping and combine it // with our other clipping state: if (IsValid()) { SetValid(InheritAppClippingAndTransform(hdc) == Ok); } // Check the ICM Mode on the hdc - The ICM state in the HDC // passed in should override the flag setting. // IcmModeOn -> screen rendering will avoid using the // DCI codepath and instead render using GDI with the ICM enabled // HDC. if(::SetICMMode(hdc, ICM_QUERY)==ICM_ON) { Context->IcmMode = IcmModeOn; } else { // If the ICM mode is off or we failed somehow to query // the ICM mode, then set it to OFF. Context->IcmMode = IcmModeOff; } } else // non-NULL hwnd { // Since the window could have CS_OWNDC style, we still have to // inherit from it. HDC hdc = ::GetDC(hwnd); if (hdc != NULL) { if (IsValid()) { SetValid(InheritAppClippingAndTransform(hdc) == Ok); } ::ReleaseDC(hwnd, hdc); } } } /******************************Public*Routine******************************\ * * Function Description: * * Create a GpGraphics class from a DpBitmap. * * Arguments: * * [IN] surface - Specifies the DpBitmap * * Return Value: * * NULL if failure * \**************************************************************************/ GpGraphics::GpGraphics(DpBitmap * surface) : BottomContext(surface->IsDisplay) { Surface = surface; BottomContext.ContainerDpiX = surface->DpiX; BottomContext.ContainerDpiY = surface->DpiY; Context = &BottomContext; Metafile = NULL; DownLevel = FALSE; Printer = FALSE; PrintInit = NULL; LockedByGetDC = 0; CreatedDevice = FALSE; GdipBitmap = NULL; Device = Globals::DesktopDevice; // Fail the creation of the destination if EpAlphaBlender // cannot convert to the DpBitmap pixel format. // The only reason to create a graphics around a bitmap is to be // able to draw onto it. If we can't convert the format to it, // we can't draw on it. if( (surface->Type != DpBitmap::GPBITMAP) || (EpAlphaBlender::IsSupportedPixelFormat(surface->PixelFormat) && surface->PixelFormat != PixelFormat8bppIndexed)) { SetValid(TRUE); } else { SetValid(FALSE); } } /******************************Public*Routine******************************\ * * Function Description: * * Check whether the HWND has windows layering set. * * Arguments: * * [IN] hwnd - Specifies the HWND * * [OUT] isLayeredWindow - Points to BOOL that returns layering property * * Return Value: * * FALSE if failure. * \**************************************************************************/ BOOL CheckWindowsLayering( HWND hwnd, BOOL *isLayered ) { BOOL bRet = TRUE; // Assume no layering. *isLayered = FALSE; // Layering is only supported on NT5 or better. if ((Globals::IsNt) && (Globals::OsVer.dwMajorVersion >= 5) && (Globals::GetWindowInfoFunction)) { WINDOWINFO wndInfo; // Initialize the structure with the appropriate size. GpMemset(&wndInfo, 0, sizeof(WINDOWINFO)); wndInfo.cbSize = sizeof(WINDOWINFO); // NTRAID#NTBUG9-385929-2001/05/05-asecchia // See JasonSch's comments in the bug report. // Perf [agodfrey]: JStall pointed out that GetWindowInfo is very // slow (he quoted 2,700,000 clocks). Much better would be // GetWindowLong(hwnd, GWL_EXSTYLE). if (Globals::GetWindowInfoFunction(hwnd, &wndInfo)) { *isLayered = ((wndInfo.dwExStyle & WS_EX_LAYERED) != 0); // An app using layered windows might only have the property set // on the topmost or root window. So if we didn't find the // layered property on the window itself, need to check the root // window. if ((!*isLayered) && (Globals::GetAncestorFunction)) { HWND hwndRoot = Globals::GetAncestorFunction(hwnd, GA_ROOT); // It's OK for GetAncestor to fail, which indicates that // hwnd is already the top level window. If it succeeds, // then hwnd is a child window and we need to check the // root for layering. if (hwndRoot) { // Perf [agodfrey]: Ditto here - GetWindowLong is better. if (Globals::GetWindowInfoFunction(hwndRoot, &wndInfo)) { *isLayered = ((wndInfo.dwExStyle & WS_EX_LAYERED) != 0); } else { WARNING(("GetWindowInfo failed")); bRet = FALSE; } } } } else { WARNING(("GetWindowInfo failed")); bRet = FALSE; } } return bRet; } /******************************Public*Routine******************************\ * * Function Description: * * Create a GpGraphics for a window. * * Arguments: * * [IN] hwnd - Specifies the window * * [IN] icmMode - Specifies the GDI ICM mode associated with this * * Return Value: * * NULL if failure. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromHwnd( HWND hwnd, HdcIcmMode icmMode ) { // If hwnd is NULL, caller really meant that desktop // window should be used (Windows convention treats NULL hwnd // as a reference to desktop window). if (hwnd == NULL) { hwnd = GetDesktopWindow(); ASSERT(hwnd != NULL); } RECT rect; // Check if hwnd has layering enabled. Need to let GpGraphics know // about it. Only on NT5 or better. Also note that GetWindowInfo // is only on NT4SP3 (or later) or Win98 (or later). BOOL isLayeredWindow; if (!CheckWindowsLayering(hwnd, &isLayeredWindow)) { WARNING(("CheckWindowsLayering failed")); return NULL; } // GetClientRect is nice and fast (entirely user-mode on NT). if (::GetClientRect(hwnd, &rect)) { ASSERT((rect.top == 0) && (rect.left == 0)); GpGraphics *g = new GpGraphics(hwnd, NULL, rect.right, rect.bottom, icmMode, isLayeredWindow); CheckValid(g); return g; } else { WARNING(("GetClientRect failed")); } return NULL; } /******************************Public*Routine******************************\ * * Function Description: * * Create a GpGraphics for a screen DC. * * Arguments: * * [IN] hdc - Specifies the DC * * Return Value: * * NULL if failure. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromGdiScreenDC( HDC hdc ) { // If hdc is NULL, caller really meant that desktop // window should be used (Windows convention treats NULL hwnd // as a reference to desktop window). if (hdc == NULL) { return GpGraphics::GetFromHwnd(NULL); } ASSERT(GetDCType(hdc) == OBJ_DC); HWND hwnd = WindowFromDC(hdc); if (hwnd != NULL) { RECT windowRect; POINT dcOrg; // Check if hwnd has layering enabled. Need to let GpGraphics know // about it. Only on NT5 or better. Also note that GetWindowInfo // is only on NT4SP3 (or later) or Win98 (or later). BOOL isLayeredWindow; if (!CheckWindowsLayering(hwnd, &isLayeredWindow)) { WARNING(("CheckWindowsLayering failed")); return NULL; } // If the user did a GetWindowFromDC call, then they want to be // able to draw to the entire window, not just to the client area. // In that case we use the WindowRect for the surface size, instead // of using the ClientRect. We determine this by seeing where the // DC origin is (the window rect or the client rect). if (::GetWindowRect(hwnd, &windowRect)) { if (::GetDCOrgEx(hdc, &dcOrg)) { if ((dcOrg.x == windowRect.left) && (dcOrg.y == windowRect.top)) { windowRect.right -= windowRect.left; windowRect.bottom -= windowRect.top; GpGraphics *g = new GpGraphics(NULL, hdc, windowRect.right, windowRect.bottom, IcmModeOff, isLayeredWindow); CheckValid(g); return g; } RECT clientRect; // GetClientRect is nice and fast (entirely user-mode on NT). if (::GetClientRect(hwnd, &clientRect)) { ASSERT((clientRect.top == 0) && (clientRect.left == 0)); GpGraphics *g = new GpGraphics(NULL, hdc, clientRect.right, clientRect.bottom, IcmModeOff, isLayeredWindow); CheckValid(g); return g; } else { WARNING(("GetClientRect failed")); } } else { WARNING(("GetDCOrgEx failed")); } } else { WARNING(("GetWindowRect failed")); } } else // WindowFromDC failed { // The client must have used CreateDC("DISPLAY") to get this hdc, // so we'll use the full bounds of the screen to create the graphics. INT screenWidth; INT screenHeight; screenWidth = ::GetDeviceCaps(hdc, HORZRES); screenHeight = ::GetDeviceCaps(hdc, VERTRES); if ((screenWidth > 0) && (screenHeight > 0)) { GpGraphics *g = new GpGraphics(NULL, hdc, screenWidth, screenHeight); CheckValid(g); return g; } } return NULL; } /**************************************************************************\ * * Function Description: * * Set the Graphics container transform to copy the transform set in * the DC. * * NOTE: This function will be called a lot, and is therefore rather * performance critical. Do not add gratuitous GDI or GDI+ * calls! * * Arguments: * * [IN] hdc - Specifies the DC to be copied * * Notes: * * This should really be a member of DpContext (bug #98174). * * Return Value: * * Ok if successful * * History: * * 12/04/1998 andrewgo * Created it. * \**************************************************************************/ GpStatus GpGraphics::InheritAppClippingAndTransform( HDC hdc ) { POINT points[3]; GpPointF destPoints[3]; GpRectF srcRect; GpRectF destRect; GpStatus infer = GenericError; GpStatus status; BYTE stackBuffer[1024]; // It would take a lot of time to call all the Win32 APIs to query // the transform: we would minimally have to call GetMapMode, // GetWindowOrgEx, and GetViewportOrgEx; and maximally also have to // call GetWorldTransform, GetViewportExtEx, and GetWindowExtEx. // // We cheat a little by making a single call to LPtoDP with a // parallelogram, and then inferring the result. Note that we do // run the risk of some error, and on Win9x of overflow, since Win9x // only supports 16-bit coordinates. To counteract this, we try to // choose large values that won't overflow. // There is a common scenario when LPtoDP will overflow returning // bad saturated values. In printing to high DPI devices to avoid // overflow on Win9x, apps will use a large translate in the // window org to reposition the graphics. In such cases, we do // the expensive work of determining the real WorldToDevice. if (!Globals::IsNt && Context->ContainerDpiX > 600.0f) { INT mapMode = GetMapMode(hdc); if (mapMode == MM_ANISOTROPIC || mapMode == MM_ISOTROPIC) { POINT viewOrg, windOrg; GetViewportOrgEx(hdc, &viewOrg); GetWindowOrgEx(hdc, &windOrg); SIZE viewExt, windExt; GetViewportExtEx(hdc, &viewExt); GetWindowExtEx(hdc, &windExt); GpRectF windRect(TOREAL(windOrg.x), TOREAL(windOrg.y), TOREAL(windExt.cx), TOREAL(windExt.cy)); GpRectF viewRect(TOREAL(viewOrg.x), TOREAL(viewOrg.y), TOREAL(viewExt.cx), TOREAL(viewExt.cy)); infer = Context->ContainerToDevice.InferAffineMatrix(viewRect, windRect); } } if (infer != Ok) { points[0].x = 0; points[0].y = 0; points[1].x = 8192; points[1].y = 0; points[2].x = 0; points[2].y = 8192; if (!LPtoDP(hdc, points, 3)) return(GenericError); srcRect.X = TOREAL(0.0); srcRect.Y = TOREAL(0.0); srcRect.Width = TOREAL(8192.0); srcRect.Height = TOREAL(8192.0); if ((points[0].x == points[2].x) && (points[0].y == points[1].y)) { // Win9x doesn't support rotation, and even on NT it will be // pretty rare. Having a special-case like this for scaling // saves us some work in 'InferAffineMatrix': destRect.X = LTOF(points[0].x); destRect.Y = LTOF(points[0].y); destRect.Width = LTOF(points[1].x - points[0].x); destRect.Height = LTOF(points[2].y - points[0].y); infer = Context->ContainerToDevice.InferAffineMatrix(destRect, srcRect); } else { destPoints[0].X = LTOF(points[0].x); destPoints[0].Y = LTOF(points[0].y); destPoints[1].X = LTOF(points[1].x); destPoints[1].Y = LTOF(points[1].y); destPoints[2].X = LTOF(points[2].x); destPoints[2].Y = LTOF(points[2].y); infer = Context->ContainerToDevice.InferAffineMatrix(destPoints, srcRect); } } if (infer != Ok) return(infer); Context->UpdateWorldToDeviceMatrix(); // Quickly get a GDI region object: HRGN regionHandle = GetCachedGdiRegion(); if (regionHandle == NULL) return(OutOfMemory); // Verify that our cache is working properly, and we have a valid region: ASSERT(GetObjectTypeInternal(regionHandle) == OBJ_REGION); // No early-outs from here-in, because we have to cleanup: status = Ok; // Query the application clip region, if there is one. The value of '1' // as a parameter is a magic value used by the metafile code on both // Win9x and NT to query the application clipping. If a value of zero // is returned, there is no application-set clipping. // // Note that if we had passed in SYSRGN (a value of '4') instead of '1', // the result does NOT include the application level clipping. (In other // words, SYSRGN is not equivalent to the Rao region, which is why we have // to explicitly query the application clipping here.) INT getResult = GetRandomRgn(hdc, regionHandle, 1); if (getResult == TRUE) { // If our stack buffer is big enough, get the clipping contents // in one gulp: INT newSize = GetRegionData(regionHandle, sizeof(stackBuffer), (RGNDATA*) &stackBuffer[0]); RGNDATA *regionBuffer = (RGNDATA*) &stackBuffer[0]; // The spec says that GetRegionData returns '1' in the event of // success, but NT returns the actual number of bytes written if // successful, and returns '0' if the buffer wasn't large enough: if ((newSize < 1) || (newSize > sizeof(stackBuffer))) { // Our stack buffer wasn't big enough. Figure out the required // size: newSize = GetRegionData(regionHandle, 0, NULL); if (newSize > 1) { regionBuffer = (RGNDATA*) GpMalloc(newSize); if (regionBuffer == NULL) return OutOfMemory; // Initialize to a decent result in the unlikely event of // failure of GetRegionData: regionBuffer->rdh.nCount = 0; GetRegionData(regionHandle, newSize, regionBuffer); } } // Set our GDI+ container clipping to be the same thing as the // GDI application clipping: status = Context->ContainerClip.Set((RECT*) ®ionBuffer->Buffer[0], regionBuffer->rdh.nCount); if (status == Ok) { // the ContainerClip must always be intersected with the WindowClip status = Context->ContainerClip.And(&WindowClip); } if (status != Ok) { // use the best fall-back solution we can // guaranteed to succeed Context->ContainerClip.Set(&WindowClip); } // Now calculate the combined result: status = this->AndVisibleClip(); // Free the temporary buffer if one was allocated: if (regionBuffer != (RGNDATA*) &stackBuffer[0]) GpFree(regionBuffer); } ReleaseCachedGdiRegion(regionHandle); return(status); } /**************************************************************************\ * * Function Description: * * Create a GpGraphics class from a bitmap DC. * * History: * * 12/06/1998 andrewgo * Created it. * * 11/21/2000 minliu * Change the way GDI+ using the palette inside the DIBSection * \**************************************************************************/ GpGraphics* GpGraphics::GetFromGdiBitmap( HDC hdc ) { ASSERT((hdc != NULL) && (GetDCType(hdc) == OBJ_MEMDC)); HBITMAP hbitmap = (HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP); if (hbitmap) { DpBitmap *bitmap = new DpBitmap(hdc); // initializes Dpi if (CheckValid(bitmap)) { INT bitmapWidth; INT bitmapHeight; EpPaletteMap* pPaletteMap = NULL; DIBSECTION dibInfo; INT infoSize = GetObjectA(hbitmap, sizeof(dibInfo), &dibInfo); BOOL initialized = FALSE; BOOL isHalftoneDIB = FALSE; DpDriver* driver = NULL; ColorPalette* pPalette = NULL; // WinNT/Win95 differences in GetObject: // // WinNT always returns the number of bytes filled, either // sizeof(BITMAP) or sizeof(DIBSECTION). // // Win95 always returns the original requested size (filling the // remainder with NULLs). So if it is a DIBSECTION, we expect // dibInfo.dsBmih.biSize != 0; otherwise it is a BITMAP. if ( (infoSize == sizeof(DIBSECTION) ) &&(Globals::IsNt || dibInfo.dsBmih.biSize != 0) ) { // If this is an 8 bpp DIB, get its color palette and make a // matching palette map from our halftone palette. if ( dibInfo.dsBmih.biBitCount == 8 ) { // Create a new EpPaletteMap object. // Note: If the colorTable is exactly the same as our // GDI+ halftone palette, we will have a 1 to 1 color // translation table in the EpPaletteMap object. If the // color palette doesn't match exactly with our GDI+ // halftone palette and also within a certain // mismatching range, we will have a translation table // in EpPaletteMap object. // Also, EpPaletteMap object will set up a IsVGAOnly() // to tell us if GDI+ can do the halftone dithering or // not (if IsVGAOnly() returns FALSE, it means GDI+ can // do it // NOTE: EpPaletteMap may allocate storage for pPalette // which must be freed with GpFree. pPaletteMap = new EpPaletteMap(hdc, &pPalette, TRUE); if ( pPaletteMap == NULL ) { WARNING(("FromGdiBmp()-new EpPaletteMap failed")); } else if ( (pPaletteMap->IsValid() == TRUE) &&(pPaletteMap->IsVGAOnly() == FALSE) ) { ASSERT(pPalette != NULL); // GDI+ can do the halftone dithering isHalftoneDIB = TRUE; } else { // The supplied palette has insufficient // matching colors for our halftone dithering, // but we can still do VGA dithering. However, // we'll use the GDI bitmap path instead, to // be safe, since this is what we were doing // before. if (pPaletteMap->IsValid()) { ASSERT(pPalette != NULL); GpFree(pPalette); pPalette = NULL; } delete pPaletteMap; pPaletteMap = NULL; } }// if ( dibInfo.dsBmih.biBitCount == 8 ) // Up to this point, we will either have isHalftoneDIB = TRUE, // which means GDI+ can do the dithering or FALSE otherwise. if ((dibInfo.dsBmih.biBitCount > 8) || (isHalftoneDIB == TRUE) ) { initialized = bitmap->InitializeForDibsection( hdc, hbitmap, Globals::DesktopDevice, &dibInfo, &bitmapWidth, &bitmapHeight, &driver ); } }// if it is a DIBSection if ( initialized == FALSE ) { // Use GDI code path bitmapWidth = dibInfo.dsBm.bmWidth; bitmapHeight = dibInfo.dsBm.bmHeight; bitmap->InitializeForGdiBitmap(Globals::DesktopDevice, bitmapWidth, bitmapHeight); driver = Globals::GdiDriver; } GpGraphics *g = new GpGraphics(bitmap); if (g) { // NTRAID#NTBUG9-370409-2001/04/17-asecchia // This is error-prone code. The GpGraphics and the DpContext // objects should properly encapsulate their own construction. g->Type = GraphicsBitmap; g->Driver = driver; g->Context->Hdc = hdc; g->Context->PaletteMap = NULL; g->Context->Palette = NULL; g->ResetState(0, 0, bitmapWidth, bitmapHeight); if (g->InheritAppClippingAndTransform(hdc) == Ok) { // If this is our special DIB, set the original palette in // the Context so that later on when we doing alpha blend // etc., we can use it to read pixel data from the // DIBSection correctly if ( isHalftoneDIB == TRUE ) { g->Context->Palette = pPalette; g->Context->PaletteMap = pPaletteMap; return(g); } else if (GetDeviceCaps(hdc, BITSPIXEL) <= 8) { ASSERT(pPaletteMap == NULL); pPaletteMap = new EpPaletteMap(hdc); if ( NULL != pPaletteMap ) { pPaletteMap->SetUniqueness( Globals::PaletteChangeCount ); if ( pPaletteMap->IsValid() ) { // Now that we know that the pPaletteMap is // valid and we're returning a valid GpGraphics // we can give up ownership of the pPaletteMap // to the GpGraphics and return without // deleting it. g->Context->PaletteMap = pPaletteMap; return(g); } } } else { // Higher than 8 bpp, this graphics object is fine return(g); } }// if (g->InheritAppClippingAndTransform(hdc) == Ok) delete g; }// if (g) else { delete bitmap; } // We fall into here only we failed to create the Graphics object if ( NULL != pPaletteMap ) { delete pPaletteMap; } if ( NULL != pPalette ) { GpFree(pPalette); } }// if (CheckValid(bitmap)) }// if ( hbitmap ) else { RIP(("GetCurrentObject failed")); } return(NULL); }// GetFromGdiBitmap() /**************************************************************************\ * * Function Description: * * Create a GpGraphics class from a GpBitmap. * * History: * * 09/22/1999 gilmanw * Created it. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromGdipBitmap( GpBitmap * bitmap, ImageInfo * imageInfo, EpScanBitmap * scanBitmap, BOOL isDisplay ) { DpBitmap *surface = new DpBitmap(); if (CheckValid(surface)) { // This call initializes the DPI and IsDisplay members surface->InitializeForGdipBitmap(imageInfo->Width, imageInfo->Height, imageInfo, scanBitmap, isDisplay); GpGraphics *g = new GpGraphics(surface); if (g) { g->Type = GraphicsBitmap; g->Driver = Globals::EngineDriver; g->Context->Hdc = NULL; g->GdipBitmap = bitmap; g->ResetState(0, 0, imageInfo->Width, imageInfo->Height); return g; } else { delete surface; } } return(NULL); } /**************************************************************************\ * * Function Description: * * Create a GpGraphics class from a direct draw surface. * * History: * * 10/06/1999 bhouse * Created it. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromDirectDrawSurface( IDirectDrawSurface7 * surface ) { INT bitmapWidth; INT bitmapHeight; GpGraphics *g; DpDriver *driver; DpBitmap *bitmap = new DpBitmap(); if (CheckValid(bitmap)) { // Leave bitmap->IsDisplay and Dpi params at their default values. if( bitmap->InitializeForD3D(surface, &bitmapWidth, &bitmapHeight, &driver)) { GpGraphics *g = new GpGraphics(bitmap); if (g) { g->Type = GraphicsBitmap; g->Driver = driver; g->Context->Hdc = NULL; g->ResetState(0, 0, bitmapWidth, bitmapHeight); return(g); } else { delete bitmap; } } } return(NULL); } /**************************************************************************\ * * Function Description: * * This should only be called by the GetFromHdc() for a printer DC. * * Arguments: * * * Return Value: * * * History: * 6/1/1999 ericvan Created it. * \**************************************************************************/ GpStatus GpGraphics::StartPrinterEMF() { IStream *stream = NULL; INT result; // Send escape to printer to determine if this supports the UNIDRV // escape codes. GDIPPRINTINIT printInit; printInit.dwSize = sizeof(GDIPPRINTINIT); // !! Query whether escape is supported first. result = ExtEscape(Context->Hdc, GDIPLUS_UNI_INIT, 0, NULL, sizeof(GDIPPRINTINIT), (LPSTR)&printInit); if (result<=0) return NotImplemented; // save printer data in structure PrintInit = new GDIPPRINTINIT; if(!PrintInit) { return OutOfMemory; } memcpy((LPVOID)PrintInit, (LPVOID)&printInit, sizeof(GDIPPRINTINIT)); PrinterEMF = GlobalAlloc(GMEM_MOVEABLE, 1); if (!PrinterEMF) { return OutOfMemory; } if (CreateStreamOnHGlobal(PrinterEMF, FALSE, &stream) != S_OK) { GlobalFree(PrinterEMF); PrinterEMF = NULL; return Win32Error; } FPUStateSaver fpuState; PrinterMetafile = new GpMetafile(stream, Context->Hdc, EmfTypeEmfPlusOnly); stream->Release(); if (!PrinterMetafile) { GlobalFree(PrinterEMF); PrinterEMF = NULL; return OutOfMemory; } PrinterGraphics = PrinterMetafile->GetGraphicsContext(); Metafile = PrinterGraphics->Metafile; return Ok; } /**************************************************************************\ * * Function Description: * * Arguments: * * Return Value: * * History: * 6/1/1999 ericvan Created it. * \**************************************************************************/ GpStatus GpGraphics::EndPrinterEMF() { LPVOID emfBlock; INT result = -1; GpStatus status; if (PrinterGraphics) { // end recording to metafile graphics context delete PrinterGraphics; PrinterGraphics = NULL; } // Disposing of metafile also Release() stream interface. if (PrinterMetafile) { PrinterMetafile->Dispose(); PrinterMetafile = NULL; Metafile = NULL; } if (PrinterEMF) { emfBlock = GlobalLock(PrinterEMF); if (emfBlock) result = ExtEscape(Context->Hdc, GDIPLUS_UNI_ESCAPE, // This is a downcast on IA64, but I don't believe // PrinterEMF will be bigger than MAXINT (ULONG)GlobalSize(PrinterEMF), (LPCSTR)emfBlock, sizeof(GpStatus), (LPSTR)&status); GlobalUnlock(PrinterEMF); GlobalFree(PrinterEMF); PrinterEMF = NULL; if (result>0) return status; else return Win32Error; } else return Ok; } /**************************************************************************\ * * Function Description: * * Uses HDC and HANDLE for printer to determine the postscript level. The * caller must ensure this is a PS HDC so we don't waste our time. * * Arguments: * * HDC - handle to device context for printer * HANDLE - handle to printer device (may be NULL) * * Return Value: * * Postscript Level - (-1) if not found, must provide downlevel support * * History: * 10/26/1999 ericvan Created it. * \**************************************************************************/ INT GpGraphics::GetPostscriptLevel(HDC hDC, HANDLE hPrinter) { // Place this code under the load library critical section. We make // extensive use of Globals::variables and need protection. LoadLibraryCriticalSection llcs; INT feature = FEATURESETTING_PSLEVEL; INT level; if ((Globals::hCachedPrinter != 0) && (Globals::hCachedPrinter == hPrinter)) { return Globals::CachedPSLevel; } // !! Re-examine this, Nolan said he would add this to the HP ps driver // so we may have this working on Win9x if (Globals::IsNt && Globals::OsVer.dwMajorVersion >= 5) { DWORD EscapeValue = POSTSCRIPT_IDENTIFY; if (ExtEscape(hDC, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue, 0, NULL) != 0) { // must be in GDI centric mode to get PS feature settings... DWORD Mode = PSIDENT_GDICENTRIC; if (ExtEscape(hDC, POSTSCRIPT_IDENTIFY, sizeof(DWORD), (LPSTR)&Mode, 0, NULL)>0) { if (ExtEscape(hDC, GET_PS_FEATURESETTING, sizeof(INT), (LPSTR)&feature, sizeof(INT), (LPSTR)&level)>0) { Globals::hCachedPrinter = hPrinter; Globals::CachedPSLevel = level; return level; } } } } if (hPrinter == NULL) return -1; // get name of the PPD file. union { DRIVER_INFO_2A driverInfo; CHAR buftmp[1024]; }; DWORD size; // we require GetPrinterDriver() API to get the .PPD path+file. // unfortunately this API is buried in winspool.drv (112KB), so we // lazy load it here. if (Globals::WinspoolHandle == NULL) { Globals::WinspoolHandle = LoadLibraryA("winspool.drv"); if (Globals::WinspoolHandle == NULL) return -1; Globals::GetPrinterDriverFunction = (WINSPOOLGETPRINTERDRIVERFUNCTION) GetProcAddress( Globals::WinspoolHandle, "GetPrinterDriverA"); } if (Globals::GetPrinterDriverFunction == NULL) return -1; if (Globals::GetPrinterDriverFunction(hPrinter, NULL, 2, (BYTE*)&driverInfo, 1024, &size) == 0) return -1; HANDLE hFile; hFile = CreateFileA(driverInfo.pDataFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return -1; // Obtain the file size // NOTE: We don't support files larger than 4GB. DWORD sizeLow, sizeHigh; sizeLow = GetFileSize(hFile, &sizeHigh); // impose a 4GB limit (certainly reasonable) if (sizeLow == 0xffffffff || sizeHigh != 0) { CloseHandle(hFile); return -1; } // Map the file into memory HANDLE hFilemap; LPSTR fileview = NULL; hFilemap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hFilemap != NULL) { fileview = (LPSTR) MapViewOfFile(hFilemap, FILE_MAP_READ, 0, 0, 0); CloseHandle(hFilemap); } else { CloseHandle(hFile); return -1; } LPSTR buf = fileview; LPSTR topbuf = fileview + (sizeLow-16); // we actually expect the LanguageLevel to be early // in the file (likely in the first 2K of data). // !! What if this appears in comments (read starting at carriage returns?! level = -1; while (buf < topbuf) { if (*buf == 'L' && GpMemcmp(buf, "LanguageLevel", 13) == 0) { while ((*buf < '0' || *buf > '9') && buf < topbuf) buf++; CHAR ch = *buf; if (ch >= '0' && ch <= '9') level = (INT)ch - (INT)'0'; break; } buf++; } UnmapViewOfFile((LPCVOID)fileview); CloseHandle(hFile); Globals::hCachedPrinter = hPrinter; Globals::CachedPSLevel = level; return level; } /**************************************************************************\ * * Function Description: * * Arguments: * * Return Value: * * History: * 6/1/1999 ericvan Created it. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromGdiPrinterDC( HDC hdc, HANDLE hPrinter ) { ASSERT((hdc != NULL) && ((GetDCType(hdc) == OBJ_DC) || (GetDCType(hdc) == OBJ_ENHMETADC))&& (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASPRINTER)); // !! Change to useNewPrinterCode when we've fully switched DriverPrint *driver = NULL; DpBitmap *bitmap = new DpBitmap(hdc); // initializes Dpi if (CheckValid(bitmap)) { GpPrinterDevice *pdevice; { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; pdevice = new GpPrinterDevice(hdc); } // FPU Sandbox for potentially unsafe FPU code. if (CheckValid(pdevice)) { // we defer creating driver until we know which to create // change to use DIBSECTION instead of GDI operations // Check if this is a postscript printer CHAR strTech[30]; strTech[0] = '\0'; INT ScaleX; // ratio of device DPI : capped DPI INT ScaleY; // It is a PostScript printer if POSTSCRIPT_PASSTHROUGH or // POSTSCRIPT_IGNORE is available. For some reason querying // GETTECHNOLOGY for postscript fails in MS Publisher, it may // be because we are in GDI centric mode. int iWant1 = POSTSCRIPT_PASSTHROUGH; int iWant2 = POSTSCRIPT_IGNORE; BOOL postscript; { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; postscript = ( (Escape(hdc, QUERYESCSUPPORT, sizeof(iWant1), (LPCSTR)&iWant1, NULL) != 0) || (Escape(hdc, QUERYESCSUPPORT, sizeof(iWant2), (LPCSTR)&iWant2, NULL) != 0)); } // FPU Sandbox for potentially unsafe FPU code. SIZEL szlDevice; szlDevice.cx = GetDeviceCaps(hdc, HORZRES); szlDevice.cy = GetDeviceCaps(hdc, VERTRES); // ScaleX and ScaleY should be power of two (2, 4, 8) if (bitmap->DpiX <= 100) { ScaleX = 1; ScaleY = 1; } else { if (bitmap->DpiX >= 1200) { ScaleX = GpRound(TOREAL(bitmap->DpiX) / 200); ScaleY = GpRound(TOREAL(bitmap->DpiY) / 200); } else { ScaleX = 3; ScaleY = 3; // cap 600 to 200 dpi or 3:1 } } // We no longer keep capped dpi -- we use the device dpi as // capped dpi so that the world to // device transformation is correct for the clipped region and // path transformation. ScaleX and ScaleY are used to scale // the output rectangle region. bitmap->InitializeForPrinter(pdevice, szlDevice.cx, szlDevice.cy); GpGraphics *g = new GpGraphics(bitmap); if (g) { g->Printer = TRUE; g->Context->Hdc = hdc; g->Context->IsPrinter = TRUE; // Note: Both 'Device' and 'Driver' are freed at // ~GpGraphics time when 'CreatedDevice' is set: g->PrinterMetafile = NULL; g->PrinterGraphics = NULL; g->PrinterEMF = NULL; if (postscript) { g->Type = GraphicsBitmap; INT level = GetPostscriptLevel(hdc, hPrinter); driver = new DriverPS(pdevice, level); // !! Should this stuff be shifted into some driver // initialization routine?! // !! Interop - what about redefines or conflicts // (conflicts aren't likely, but are // theoretically possible with // GetDC/ReleaseDC) } #if 0 else if (g->StartPrinterEMF() == Ok) { g->Type = GraphicsMetafile; RIP(("Setting CreatedDevice will free Driver")); driver = NULL; new DriverMeta(pdevice); // GDI has some optimization code to check the page for color // content, if none is found, on play back, it sets the device // as being monochrome. // // Unfortunately, since our stuff is encoded in escapes, we end up // playing back in monochrome. The work-around, is to call a GDI // API that forces a color flag to be set in the EMF code. A // simple one is SetTextColor(). // !!! Might want to remove this SetTextColor stuff for version one: COLORREF colorRef = GetTextColor(hdc); SetTextColor(hdc, 0x00808080); SetTextColor(hdc, colorRef); } #endif else { // We can't use escapes for optimization, // Map to GDI HDC operations. g->Type = GraphicsBitmap; driver = new DriverNonPS(pdevice); } if (CheckValid(driver)) { g->Driver = driver; g->Device = pdevice; driver->ScaleX = ScaleX; driver->ScaleY = ScaleY; // check for supporting print clip escapes // These may be supported on PCL as well as Postscript DWORD EscapeValue1 = CLIP_TO_PATH; DWORD EscapeValue2 = BEGIN_PATH; DWORD EscapeValue3 = END_PATH; // Although some PCL drivers support CLIP_TO_PATH we // currently disable their use due to some outstanding // bugs in HP and Lexmark PCL drivers. See bug #182972 { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; driver->UseClipEscapes = postscript && (ExtEscape(hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue1, 0, NULL) != 0) && (ExtEscape(hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue2, 0, NULL) != 0) && (ExtEscape(hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue3, 0, NULL) != 0); } // FPU Sandbox for potentially unsafe FPU code. DWORD EscapeValue = CHECKJPEGFORMAT; { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; driver->SupportJPEGpassthrough = ExtEscape( hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue, 0, NULL) != 0; } // FPU Sandbox for potentially unsafe FPU code. EscapeValue = CHECKPNGFORMAT; { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; driver->SupportPNGpassthrough = ExtEscape( hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue, 0, NULL) != 0; } // FPU Sandbox for potentially unsafe FPU code. driver->NumColors = GetDeviceCaps(hdc, NUMCOLORS); driver->UseVDP = FALSE; // !! VDP not supported in v1 //VDP_GetFormSupport(hdc, // (WORD*)&(driver->SupportVDP)); g->CreatedDevice = TRUE; g->ResetState(0, 0, szlDevice.cx, szlDevice.cy); if (g->InheritAppClippingAndTransform(hdc) == Ok) { return(g); } else { // ~GpGraphics implicitly deletes bitmap, device, and driver delete g; return NULL; } } delete g; delete pdevice; return NULL; } delete pdevice; } delete bitmap; } return NULL; } /**************************************************************************\ * * Function Description: * * This constructor is used internally for printer callback routine. * * Arguments: * * * Return Value: * * * History: * 6/1/1999 ericvan Created it. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromHdcSurf( HDC hdc, SURFOBJ* surfObj, RECTL* bandClip ) { static UINT16 PixelCount[] = { 1, 4, 8, 16, 24, 32 }; INT width; INT height; GpGraphics* g; DpDriver *driver; // This is a weird surface. It is a banded surface, so we set up a // clip and direct bits pointer. We also have an HDC for it if we // decide to punt to GDI. DpBitmap *bitmap = new DpBitmap(hdc); // initializes Dpi if (CheckValid(bitmap)) { GpGraphics *g = new GpGraphics(bitmap); if (g) { width = GetDeviceCaps(hdc, HORZRES); height = GetDeviceCaps(hdc, VERTRES); // create DIB section for direct rendering of bits if (surfObj->iBitmapFormat < BMF_1BPP || surfObj->iBitmapFormat > BMF_32BPP) { InitializeHdcOnlyUse: // we don't support direct rendering to this type of // surface format. Do everything through GDI HDC. driver = Globals::GdiDriver; bitmap->InitializeForGdiBitmap(Globals::DesktopDevice, width, height); g->Type = GraphicsBitmap; g->Driver = driver; g->Context->Hdc = hdc; g->ResetState(0, 0, 1, 1); } else { DIBSECTION dibSec; dibSec.dsBm.bmType = 0; dibSec.dsBm.bmWidth = surfObj->sizlBitmap.cx; if (surfObj->lDelta < 0) { // bits pointer at top of frame buffer (scans down) dibSec.dsBm.bmWidthBytes = -surfObj->lDelta; dibSec.dsBm.bmHeight = -surfObj->sizlBitmap.cy; } else { // bits pointer at base of frame buffer (scans up) dibSec.dsBm.bmWidthBytes = surfObj->lDelta; dibSec.dsBm.bmHeight = surfObj->sizlBitmap.cy; } dibSec.dsBm.bmPlanes = 1; dibSec.dsBm.bmBitsPixel = PixelCount[surfObj->iBitmapFormat-1]; dibSec.dsBmih.biSize = sizeof(BITMAPINFOHEADER); dibSec.dsBmih.biWidth = width; dibSec.dsBmih.biHeight = height; dibSec.dsBmih.biPlanes = 1; dibSec.dsBmih.biBitCount = PixelCount[surfObj->iBitmapFormat-1]; dibSec.dsBmih.biCompression = BI_BITFIELDS; dibSec.dsBmih.biSize = 0; dibSec.dsBitfields[0] = 0x000000FF; dibSec.dsBitfields[1] = 0x0000FF00; dibSec.dsBitfields[2] = 0x00FF0000; if (bitmap->InitializeForDibsection( hdc, (HBITMAP) NULL, Globals::DesktopDevice, &dibSec, &width, &height, &driver) == FALSE) goto InitializeHdcOnlyUse; // Init Valid now so later calls won't fail g->Type = GraphicsBitmap; g->Driver = driver; g->Context->Hdc = hdc; // How do we clip and map to correct band? // GDI has set the WorldToContainer transform to translate the // correct band to position (0,0) on the device surface. So we // clip the size of the band relative to the surface. The image // is mapped via the transform into this clipped region. // set visible client clip region for surface g->ResetState(0, 0, // bandClip->left, bandClip->top, bandClip->right - bandClip->left, bandClip->bottom - bandClip->top); // Set the destination Graphics to represent device co-ordinates g->SetPageTransform(UnitPixel); if (g->InheritAppClippingAndTransform(hdc) == Ok) { return(g); } else { delete g; } } } } return(NULL); } GpGraphics* GpGraphics::GetFromGdiEmfDC( HDC hdc ) { ASSERT (hdc != NULL); DpBitmap *bitmap = new DpBitmap(hdc); // initializes Dpi if (CheckValid(bitmap)) { bitmap->InitializeForMetafile(Globals::DesktopDevice); GpGraphics *g = new GpGraphics(bitmap); if (g) { g->Type = GraphicsMetafile; g->DownLevel = TRUE; g->Driver = Globals::MetaDriver; g->Context->Hdc = hdc; g->Context->IsEmfPlusHdc = TRUE; g->ResetState(0, 0, 1, 1); // Override some state, as we don't want anything to be // clipped out of a metafile, unless there is clipping // in the hdc. g->WindowClip.SetInfinite(); g->Context->ContainerClip.SetInfinite(); g->Context->VisibleClip.SetInfinite(); if (g->InheritAppClippingAndTransform(hdc) == Ok) { return g; } delete g; } } return NULL; } /**************************************************************************\ * * Function Description: * * * Arguments: * * * Return Value: * * * History: * * \**************************************************************************/ GpGraphics* GpGraphics::GetForMetafile( IMetafileRecord * metafile, EmfType type, HDC hdc ) { ASSERT ((metafile != NULL) && (hdc != NULL)); DpBitmap *bitmap = new DpBitmap(hdc); // initializes Dpi if (CheckValid(bitmap)) { bitmap->InitializeForMetafile(Globals::DesktopDevice); GpGraphics *g = new GpGraphics(bitmap); if (g) { g->Type = GraphicsMetafile; g->Metafile = metafile; g->DownLevel = (type != EmfTypeEmfPlusOnly); g->Driver = Globals::MetaDriver; g->Context->Hdc = hdc; g->Context->IsEmfPlusHdc = TRUE; g->ResetState(0, 0, 1, 1); // Override some state, as we don't want anything to be // clipped out of a metafile g->WindowClip.SetInfinite(); g->Context->ContainerClip.SetInfinite(); g->Context->VisibleClip.SetInfinite(); return(g); } } return(NULL); } /**************************************************************************\ * * Function Description: * * Create a GpGraphics class from a DC. * * Arguments: * * [IN] hdc - Specifies the DC. * * Return Value: * * NULL if failure (such as with an invalid DC). * * History: * * 12/04/1998 andrewgo * Created it. * \**************************************************************************/ GpGraphics* GpGraphics::GetFromHdc( HDC hdc, HANDLE hDevice ) { GpGraphics *g = NULL; // GetObjectType is nice and fast (entirely user-mode on NT): switch (GetDCType(hdc)) { case OBJ_DC: if (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASPRINTER ) { g = GpGraphics::GetFromGdiPrinterDC(hdc, hDevice); } else { g = GpGraphics::GetFromGdiScreenDC(hdc); } break; case OBJ_MEMDC: g = GpGraphics::GetFromGdiBitmap(hdc); break; case OBJ_ENHMETADC: // When metafile spooling, the printer DC will be of type // OBJ_ENHMETADC on Win9x and NT4 (but not NT5 due to a fix // to NT bug 98810). We need to do some more work to figure // out whether it's really a printer DC or a true metafile // DC: BOOL printDC; { // FPU Sandbox for potentially unsafe FPU code. FPUStateSandbox fpsb; printDC = Globals::GdiIsMetaPrintDCFunction(hdc); } // FPU Sandbox for potentially unsafe FPU code. if (printDC) { g = GpGraphics::GetFromGdiPrinterDC(hdc, hDevice); } else { g = GpGraphics::GetFromGdiEmfDC(hdc); } break; case OBJ_METADC: TERSE(("16-bit metafile DC support not yet implemented")); break; } return(g); } /**************************************************************************\ * * Function Description: * * Dispose of a GpGraphics object * * Arguments: * * NONE * * Return Value: * * NONE * \**************************************************************************/ GpGraphics::~GpGraphics() { // How do we ensure that no one is using the graphics when we are here? // Flush any pending drawing commands before we go out of scope: Flush(FlushIntentionFlush); if (IsPrinter()) { EndPrinterEMF(); if (PrintInit) delete PrintInit; } BOOL doResetHdc = TRUE; // Handle Graphics-type specific functionality: switch (Type) { case GraphicsMetafile: if (Metafile != NULL) { Metafile->EndRecording(); doResetHdc = FALSE; // EndRecording closes the metafile HDC } // FALLTHRU case GraphicsBitmap: // if this is created on a GdipBitmap, dec the ref count // delete the bitmap if ref count <= 0 if (GdipBitmap) { GdipBitmap->Dispose(); } // We have to delete the temporary surface that we created: delete Surface; break; } // Restore the DC that we were derived from (if any). // We must NOT do this before the call to EndRecording because // EndRecording needs the context->Hdc to be in the saved state // so that the transforms are still reset like GDI+ requires. if (doResetHdc) { Context->ResetHdc(); } // Free any device and driver objects that had to be created only for the // lifespan of the Graphics object: if (CreatedDevice) { delete Driver; delete Device; } SetValid(FALSE); // so we don't use a deleted object } /**************************************************************************\ * * Function Description: * * Return a GDI DC handle associated with the current graphics context. * * Arguments: * * NONE * * Return Value: * * GDI DC handle associated with the current graphics context * NULL if there is an error * * NOTE: We assume the caller has already obtained a lock on the * current graphics context. * * NOTE: This function does not return a clean DC! That is, expect it * to have a funky transform, weird ROP mode, etc. If you want * to use this internally, you should probably call Context->GetHdc() * directly. * \**************************************************************************/ HDC GpGraphics::GetHdc() { // We must flush the output of the Graphics before returning an HDC. this->Flush(FlushIntentionFlush); HDC hdc = NULL; if (Context->Hdc) { // If the Graphics was originally derived from an HDC, we simply // return back the original HDC (this avoids some issues as to // what to do about inherited transforms, etc.). We may have // mucked with some of the DC state, though, so reset it back to // what it was originally: Context->ResetHdc(); hdc = Context->Hdc; } else if (Context->Hwnd) { // The Graphics was originally derived from an HWND: hdc = GetDC(Context->Hwnd); } else if (Surface && (Surface->Type == DpBitmap::CreationType::GPBITMAP)) { // The GpBitmap is accessible from the EpScanBitmap. It will // create an HDC and GDI bitmap appropriate for interop. EpScanBitmap *scan = static_cast(Surface->Scan); hdc = scan->GetBitmap()->GetHdc(); } if (IsRecording() && (hdc != NULL)) { if (IsPrinter()) { EndPrinterEMF(); } else { GpStatus status = Metafile->RecordGetDC(); } } return(hdc); } /**************************************************************************\ * * Function Description: * * Release the GDI DC handle associated with the current graphics context. * * Arguments: * * hdc - GDI DC handle returned by a previous GetHdc() call * * Return Value: * * NONE * * Notes: * * We assume the caller has already obtained a lock on the * current graphics context. * \**************************************************************************/ VOID GpGraphics::ReleaseHdc( HDC hdc ) { if (Context->Hdc) { // The Graphics was originally derived from an HDC. We don't // have to do anything here; ResetHdc() already marked the // DC as dirty. } else if (Context->Hwnd) { // The Graphics was originally derived from an HWND: ReleaseDC(Context->Hwnd, hdc); } else if (Surface && (Surface->Type == DpBitmap::CreationType::GPBITMAP)) { // The GpBitmap is accessible from the EpScanBitmap. EpScanBitmap *scan = static_cast(Surface->Scan); scan->GetBitmap()->ReleaseHdc(hdc); } if (IsRecording() && IsPrinter()) { StartPrinterEMF(); } } /**************************************************************************\ * * Function Description: * * Save (push) the graphics context state. Return the ID of the current * state (before the push) for the app to restore to later on. * * Arguments: * * NONE * * Return Value: * * gstate - the state to restore the context to later * * Created: * * 3/4/1999 DCurtis * \**************************************************************************/ INT GpGraphics::Save() { DpContext * newContext = new DpContext(Context); if ((newContext != NULL) && (newContext->AppClip.Set(&(Context->AppClip), TRUE) == Ok) && (newContext->ContainerClip.Set(&(Context->ContainerClip), TRUE) == Ok) && (newContext->VisibleClip.Set(&(Context->VisibleClip), TRUE) == Ok)) { INT gstate = newContext->Id; newContext->InverseOk = Context->InverseOk; newContext->PageUnit = Context->PageUnit; newContext->PageScale = Context->PageScale; newContext->PageMultiplierX = Context->PageMultiplierX; newContext->PageMultiplierY = Context->PageMultiplierY; newContext->WorldToPage = Context->WorldToPage; newContext->ContainerToDevice = Context->ContainerToDevice; newContext->WorldToDevice = Context->WorldToDevice; newContext->DeviceToWorld = Context->DeviceToWorld; newContext->IcmMode = Context->IcmMode; newContext->GdiLayered = Context->GdiLayered; Context->Next = newContext; Context = newContext; if (IsRecording()) { GpStatus status = Metafile->RecordSave(gstate); if (status != Ok) { SetValid(FALSE); // Prevent any more recording } } return gstate; } delete newContext; return 0; } #define CONTAINER_ID 0x00008000 /**************************************************************************\ * * Function Description: * * Restore (pop) the context to the state before the specified one. * * Arguments: * * gstate - the pushed state (restore to state before this) * * Return Value: * * NONE * * Created: * * 3/4/1999 DCurtis * \**************************************************************************/ VOID GpGraphics::Restore( INT gstate ) { DpContext * cur = Context; DpContext * prev; for (;;) { if ((prev = cur->Prev) == NULL) { return; } if (cur->Id == (UINT)gstate) { // don't double record EndContainer calls if (IsRecording() && ((gstate & CONTAINER_ID) == 0)) { GpStatus status = Metafile->RecordRestore(gstate); if (status != Ok) { SetValid(FALSE); // Prevent any more recording } } prev->Next = NULL; prev->SaveDc = cur->SaveDc; Context = prev; delete cur; return; } cur = prev; } } /**************************************************************************\ * * Function Description: * * End a container. Restores the state back to what it was before the * container was started. The CONTAINER_ID bit is used to make sure that * Restore is not used with BeginContainer and that EndContainer is not * used with Save. * * Arguments: * * [IN] containerState - the pushed container state * * Return Value: * * NONE * * Created: * * 4/7/1999 DCurtis * \**************************************************************************/ VOID GpGraphics::EndContainer( INT containerState ) { if (IsRecording()) { GpStatus status = Metafile->RecordEndContainer(containerState); if (status != Ok) { SetValid(FALSE); // Prevent any more recording } } Restore(containerState | CONTAINER_ID); } /**************************************************************************\ * * Function Description: * * Begin a container. This sets the container transform and the * container clip based on the current transform and the current clip. * * We have to have a container transform for 2 reasons: * 1) If we tried to do it in the world transform, then a call * to (Re)SetWorldTransform would erase the container transform. * * 2) We have APIs for setting the text size and the line width that * are based on the page units, so they are not affected by the * world transform, but they are affected by the container transform. * * Arguments: * * NONE * * Return Value: * * gstate - the state to restore the context to later * * Created: * * 3/9/1999 DCurtis * \**************************************************************************/ INT GpGraphics::BeginContainer( const GpRectF & destRect, const GpRectF & srcRect, GpPageUnit srcUnit, REAL srcDpiX, // only set by metafile enumeration REAL srcDpiY, BOOL srcIsDisplay ) { GpMatrix identityMatrix; DpContext * newContext = new DpContext(Context); if (newContext == NULL) { return 0; } // leave newContext->AppClip set to identity if ((Context->AppClip.UpdateDeviceRegion(&identityMatrix) == Ok) && (newContext->ContainerClip.Set(&(Context->AppClip.DeviceRegion), TRUE) == Ok) && (newContext->ContainerClip.And(&(Context->ContainerClip)) == Ok) && (newContext->VisibleClip.Set(&(Context->VisibleClip), TRUE) == Ok)) { REAL unitMultiplierX; REAL unitMultiplierY; GpRectF deviceSrc; newContext->GetPageMultipliers(&unitMultiplierX, &unitMultiplierY, srcUnit); deviceSrc.X = unitMultiplierX * srcRect.X; deviceSrc.Y = unitMultiplierY * srcRect.Y; deviceSrc.Width = unitMultiplierX * srcRect.Width; deviceSrc.Height = unitMultiplierY * srcRect.Height; if (newContext->ContainerToDevice.InferAffineMatrix( destRect, deviceSrc) == Ok) { newContext->AntiAliasMode = 0; newContext->TextRenderHint = TextRenderingHintSystemDefault; newContext->TextContrast = DEFAULT_TEXT_CONTRAST; newContext->CompositingMode = CompositingModeSourceOver; newContext->CompositingQuality = CompositingQualityDefault; newContext->FilterType = InterpolationModeDefaultInternal; newContext->PixelOffset = PixelOffsetModeDefault; // Note that the world to device transform includes the previous // container to device transform. newContext->ContainerToDevice.Append(Context->WorldToDevice); newContext->InverseOk = FALSE; newContext->PageUnit = UnitDisplay; newContext->PageScale = 1.0f; if ((srcDpiX > 0.0f) && (srcDpiY > 0.0f)) { // When playing a metafile, we have to guarantee that // a unit inch is played now as it would have been // when it was recorded. For example, if we recorded // the metafile at 300 dpi, then an inch was 300 pixels. // Even if we're playing it back to a 96-dpi display, // that metafile inch must still be transformed to // 300 pixels before going throught the container // transformation, so that all graphics are scaled // the same, whether pixel units or some other units. newContext->ContainerDpiX = srcDpiX; newContext->ContainerDpiY = srcDpiY; newContext->IsDisplay = srcIsDisplay; } newContext->GetPageMultipliers(); newContext->WorldToPage.Reset(); // Have to inherit the ICM and layering state: newContext->IcmMode = Context->IcmMode; newContext->GdiLayered = Context->GdiLayered; INT containerState = newContext->Id; newContext->Id |= CONTAINER_ID; Context->Next = newContext; Context = newContext; if (IsRecording()) { GpStatus status = Metafile->RecordBeginContainer(destRect, srcRect, srcUnit, containerState); if (status != Ok) { SetValid(FALSE); // Prevent any more recording } } // Do this after switching over the context! Context->UpdateWorldToDeviceMatrix(); return containerState; } } delete newContext; return 0; } /**************************************************************************\ * * Function Description: * * Begin a container. This sets the container transform and the * container clip based on the current transform and the current clip. * * We have to have a container transform for 2 reasons: * 1) If we tried to do it in the world transform, then a call * to (Re)SetWorldTransform would erase the container transform. * * 2) We have APIs for setting the text size and the line width that * are based on the page units, so they are not affected by the * world transform, but they are affected by the container transform. * * Arguments: * * NONE * * Return Value: * * gstate - the state to restore the context to later * * Created: * * 3/9/1999 DCurtis * \**************************************************************************/ INT GpGraphics::BeginContainer( BOOL forceIdentityTransform, // only set by metafile player REAL srcDpiX, REAL srcDpiY, BOOL srcIsDisplay ) { GpMatrix identityMatrix; DpContext * newContext = new DpContext(Context); if (newContext == NULL) { return 0; } // leave newContext->AppClip set to identity if ((Context->AppClip.UpdateDeviceRegion(&identityMatrix) == Ok) && (newContext->ContainerClip.Set(&(Context->AppClip.DeviceRegion), TRUE) == Ok) && (newContext->ContainerClip.And(&(Context->ContainerClip)) == Ok) && (newContext->VisibleClip.Set(&(Context->VisibleClip), TRUE) == Ok)) { // Note that the world to device transform includes the previous // container to device transform. GpMatrix worldToDevice = Context->WorldToDevice; // We append the world to device transform below, which already // has the container transform in it. We don't want to apply // the same transform twice, so we need to reset the container // transform here. newContext->ContainerToDevice.Reset(); // When playing a GDI+ metafile into another metafile, we have to guarantee // that the transform is the identity so that the GDI+ records don't get // transformed by GDI+ and then get transformed again by GDI. if (forceIdentityTransform) { worldToDevice.Reset(); } else { // The coordinates that the container transform is expecting are // world coordinates, but they will already have gone through // the new page to device transform, so convert from device // units back to page units before running them through the // the container to device transform. // In the routine above, this is done through an inferred transform // between device unit src rect and world unit dest rect. newContext->ContainerToDevice.Scale(1.0f / Context->PageMultiplierX, 1.0f / Context->PageMultiplierY); } newContext->AntiAliasMode = 0; newContext->TextRenderHint = TextRenderingHintSystemDefault; newContext->TextContrast = DEFAULT_TEXT_CONTRAST; newContext->CompositingMode = CompositingModeSourceOver; newContext->CompositingQuality = CompositingQualityDefault; newContext->FilterType = InterpolationModeDefaultInternal; newContext->PixelOffset = PixelOffsetModeDefault; newContext->ContainerToDevice.Append(worldToDevice); newContext->InverseOk = FALSE; newContext->PageUnit = UnitDisplay; newContext->PageScale = 1.0f; if ((srcDpiX > 0.0f) && (srcDpiY > 0.0f)) { // When playing a metafile, we have to guarantee that // a unit inch is played now as it would have been // when it was recorded. For example, if we recorded // the metafile at 300 dpi, then an inch was 300 pixels. // Even if we're playing it back to a 96-dpi display, // that metafile inch must still be transformed to // 300 pixels before going throught the container // transformation, so that all graphics are scaled // the same, whether pixel units or some other units. newContext->ContainerDpiX = srcDpiX; newContext->ContainerDpiY = srcDpiY; newContext->IsDisplay = srcIsDisplay; } newContext->GetPageMultipliers(); newContext->WorldToPage.Reset(); // Have to inherit the ICM and layering state: newContext->IcmMode = Context->IcmMode; newContext->GdiLayered = Context->GdiLayered; INT containerState = newContext->Id; newContext->Id |= CONTAINER_ID; Context->Next = newContext; Context = newContext; if (IsRecording()) { GpStatus status = Metafile->RecordBeginContainer(containerState); if (status != Ok) { SetValid(FALSE); // Prevent any more recording } } // Do this after switching over the context! Context->UpdateWorldToDeviceMatrix(); return containerState; } delete newContext; return 0; }