/* * Wincap.c * * Windows Screen Capture Utility * Version 3.00 * * Description: * ------------ * * Captures portions of the screen, specific windows, or the entire screen * and saves it to a file or prints it. Uses DIBAPI functions to do most * of the capture/printing/saving work. See the file DIBAPI.TXT for a * description of the DIB api functions. * * Development Team: Mark Bader * Patrick Schreiber * Garrett McAuliffe * Eric Flo * Tony Claflin * * Written by Microsoft Product Support Services, Developer Support. * Copyright (c) 1991 Microsoft Corporation. All rights reserved. */ #include #include #include "wincap.h" #include "dialogs.h" #include "dibapi.h" #include "errors.h" char szAppName[20]; // Name of application - used in dialog boxes /* Global variables */ HWND ghInst; /* Handle to instance */ HWND ghWndMain; /* Handle to main window */ FARPROC lpfnKeyHook; // Used in keyboard hook FARPROC lpfnOldHook; // Used for keyboard hook HWND hModelessDlg; // Handle to modeless dialog box /* Macro to swap two values */ #define SWAP(x,y) ((x)^=(y)^=(x)^=(y)) /************************************************************************** * * WinMain() * * Entry point of our Application. * *************************************************************************/ int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; WNDCLASS wndclass; HWND hWnd; strcpy(szAppName, "WinCap"); // Name of our App hModelessDlg = NULL; // Set handle to modeless dialog to NULL because // we haven't created it yet if (!hPrevInstance) { wndclass.style = 0; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(hInstance, "WINCAP"); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = COLOR_WINDOW + 1; wndclass.lpszMenuName = (LPSTR)NULL; wndclass.lpszClassName = (LPSTR)szAppName; if (!RegisterClass(&wndclass)) return FALSE; ghInst = hInstance; // Set Global variable /* * Create a main window for this application instance - but don't * display it. */ hWnd = CreateWindow(szAppName, // Name of the window's class "Screen Capture", // Text for window caption WS_OVERLAPPEDWINDOW, // Window Style CW_USEDEFAULT, // Default horizontal position CW_USEDEFAULT, // Default vertical position CW_USEDEFAULT, // Default width CW_USEDEFAULT, // Default height NULL, // Overlapped windows have no parent NULL, // Use the window class menu hInstance, // This instance owns this window NULL); // Pointer (not used) ghWndMain = hWnd; // Set global variable /* * We want to keep the window iconic, so let's make sure that it * starts out iconic (by using SW_SHOWMINNOACTIVE), and we also * trap the WM_QUERYOPEN message in our main message loop. These * two things together will keep our window iconic. */ ShowWindow(hWnd, SW_SHOWMINNOACTIVE); UpdateWindow(hWnd); /* * Set up the Keyboard hook for our hotkey */ lpfnKeyHook = MakeProcInstance((FARPROC)KeyboardHook, hInstance); lpfnOldHook = SetWindowsHook(WH_KEYBOARD, lpfnKeyHook); } /* * If another instance of our program is already running, let the * user know about it then exit. */ if (hPrevInstance) { MessageBox(NULL, "WinCap is already running. " "There is no need to invoke it twice.", szAppName, MB_OK | MB_ICONHAND); return FALSE; } /* Polling messages from event queue */ while (GetMessage(&msg, NULL, 0, 0)) { if (hModelessDlg == NULL || !IsDialogMessage(hModelessDlg, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } /*********************************************************************** * * KeyboardHook() * * This is the Keyboard Hook function which windows will call every * time it gets a keyboard message. In this function, we check to * see if the key pressed was Ctrl+Alt+F9, and if it is, we post * the proper message to our main window which will call up the * printscreen dialog box. * **********************************************************************/ DWORD FAR PASCAL KeyboardHook(int iCode, WORD wParam, LONG lParam) { if (iCode == HC_ACTION && wParam == VK_F9 && GetKeyState(VK_SHIFT) < 0 && GetKeyState(VK_CONTROL) < 0) { if (HIWORD (lParam) & 0x8000) PostMessage(ghWndMain, WM_PRTSC, 0, 0L); return TRUE; } else return DefHookProc(iCode, wParam, lParam, &lpfnOldHook); } /*********************************************************************** * * WndProc() * * This is our main window procedure. It receives all the messages destined * for our application's main window. * **********************************************************************/ long FAR PASCAL WndProc(HWND hWnd, WORD wMessage, WORD wParam, LONG lParam) { /* * The bNowPrinting variable is set to TRUE if we are in the middle of * printing. This takes care of the case when the user presses the hotkey * during capturing */ static BOOL bNowPrinting = FALSE; static HANDLE hOptionStruct; // handle to pass OPTIONSTRUCT to dialog box switch (wMessage) { case WM_CREATE: { HANDLE hSysMenu; FARPROC lpfnDIALOGSMsgProc; LPOPTIONSTRUCT lpOptionStruct; /* * Since we want our main window to stay iconized, the user will never * see any menus we add to our main window, so instead let's add the * menu items to the system menu, which can be called up by clicking * once on the icon. */ hSysMenu = GetSystemMenu(hWnd, FALSE); AppendMenu(hSysMenu, MF_SEPARATOR, 0, NULL); AppendMenu(hSysMenu, MF_STRING, IDM_ABOUT, "&About WinCap..."); AppendMenu(hSysMenu, MF_STRING, IDM_CAPTURE, "Ca&pture Screen..."); /* * Put up InfoBox to let user know that we loaded. */ lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC)InfoBoxDlgProc, ghInst); DialogBox(ghInst, (LPSTR)"InfoBox", hWnd, lpfnDIALOGSMsgProc); FreeProcInstance(lpfnDIALOGSMsgProc); /* * Allocate memory for an OPTIONSTRUCT. This structure will be used to * pass data to and receive data from the Options dialog box. */ hOptionStruct = GlobalAlloc(GMEM_MOVEABLE, sizeof(OPTIONSTRUCT) + 5); /* * Now set up default options */ lpOptionStruct = (LPOPTIONSTRUCT)GlobalLock(hOptionStruct); if (hOptionStruct) { lpOptionStruct->iOptionArea = IDC_SINGLEWINDOW; lpOptionStruct->iOptionWindow = IDC_ENTIREWINDOW; lpOptionStruct->iOptionDest = OPTION_PRINTER; lstrcpy(lpOptionStruct->szFileName, (LPSTR)"c:\\capture.bmp"); lpOptionStruct->iOptionPrint = IDC_BESTFIT; lpOptionStruct->iXScale = 1; lpOptionStruct->iYScale = 2; GlobalUnlock(hOptionStruct); } } break; case WM_PRTSC: { /* * The WM_PRTSC message is one that we defined in our header file. This * message is sent to us when the user wants to capture the screen, either * by hitting the hotkey (see the KeyboardHook procedure above), by * double-clicking on the icon caption (see WM_QUERYOPEN message case * below), or by selecting the menu item "Capture Screen..." (see * WM_SYSCOMMAND message case below). */ /* Check to see that we aren't already in the middle of printing. * This could happen if the user presses our hotkey in the middle of * one of our dialog boxes. */ if (bNowPrinting) { MessageBox(NULL, "Already capturing screen.", szAppName, MB_OK | MB_ICONEXCLAMATION); } else { // Commence screen capture! bNowPrinting = TRUE; DoCapture(hOptionStruct); bNowPrinting = FALSE; } } /* End WM_PRTSC case */ break; case WM_QUERYOPEN: /* * This code makes the window stay iconic. The PostMessage() is here * so that when user double-clicks on icon, we do the printscreen. */ PostMessage(hWnd, WM_PRTSC, 0, 0L); return FALSE; case WM_SYSCOMMAND: switch (wParam) { case IDM_ABOUT: /* * Display "About" Box */ { FARPROC lpfnDIALOGSMsgProc; lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC)AboutDlgProc, ghInst); DialogBox(ghInst, (LPSTR)"About", hWnd, lpfnDIALOGSMsgProc); FreeProcInstance(lpfnDIALOGSMsgProc); } return 0; case IDM_CAPTURE: /* * User selected "Capture Screen..." From the menu */ PostMessage(hWnd, WM_PRTSC, 0, 0L); return 0; } return DefWindowProc(hWnd, wMessage, wParam, lParam); break; case WM_DESTROY: /* * Clean up */ UnhookWindowsHook(WH_KEYBOARD, lpfnKeyHook); /* * Free memory used for OPTIONSTRUCT */ GlobalFree(hOptionStruct); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, wMessage, wParam, lParam); } return 0L; } /*********************************************************************** * * DoCapture() * * This procedure gets called when the user wants to capture the * screen. This is where we actually bring up the proper dialog * boxes and call the proper screen capture functions. * **********************************************************************/ void DoCapture(HANDLE hOptionStruct) { FARPROC lpfnDIALOGSMsgProc; // Pointer for dialog boxes int nResult; // return codes from the dialog boxes LPOPTIONSTRUCT lpOptionStruct; // Pointer to OPTIONSTRUCT static HDIB hDib; // Handle to our captured screen DIB char szWindowText[100]; // Text which tells what we captured static HWND hWndCurrent; /* * Keep track of the active window so we can bring it to top * later. */ hWndCurrent = GetActiveWindow(); /* * Call up Options dialog box */ lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC)OptionsDlgProc, ghInst); /* * Pass the handle to our OPTIONSTRUCT to the dialog box in the 5th parameter * to DialogBoxParam. This will be passed to our dialog box in the lParam * WM_INITDIALOG message. Since the 5th parameter to DialogBoxParam() * is 32 bits, and our handle is only 16 bits, let's put it in the * LOWORD() of the parameter. */ nResult = DialogBoxParam(ghInst, (LPSTR)"Options", ghWndMain, lpfnDIALOGSMsgProc, (DWORD)MAKELONG(hOptionStruct, 0)); FreeProcInstance(lpfnDIALOGSMsgProc); /* * If user presses OK (nResult == TRUE), then go on, otherwise don't go on */ if (nResult) { lpOptionStruct = (LPOPTIONSTRUCT)GlobalLock(hOptionStruct); if (!lpOptionStruct) return; /* * The structure should now contain: * 1. iOptionArea - Specifies which area user wants to capture * One of: IDC_SINGLEWINDOW * IDC_ENTIRESCREEN * IDC_PARTIALSCREEN * 2. iOptionWindow - Specifies which portion of the window to capture - * this will only contain valid data if the iOptionArea is set to * IDC_SINGLEWINDOW. Will be one of: * IDC_ENTIREWINDOW * IDC_CLIENTAREAONLY * IDC_CAPTIONBARONLY * IDC_MENUBARONLY * 3. iOptionDest - Bitfield which specifies the destination of the * bitmap. Can be a logical combination of: OPTION_FILE and * OPTION_PRINTER. * 4. szFileName - string which contains the filename the user * typed into as the name to save the bitmap to. This is only * valid if the iOptionDest has the OPTION_FILE bit set. * 5. iOptionPrint - Print Options. Will be one of: * IDC_BESTFIT * IDC_STRETCHTOPAGE * IDC_SCALE * 6. iXScale, iYScale - X and Y scaling factors for printing bitmap. * These values are only valid if iOptionPrint is set to IDC_SCALE. */ switch (lpOptionStruct->iOptionArea) { case IDC_ENTIRESCREEN: /* * Copy Entire screen to DIB */ { RECT rScreen; // Rect containing entire screen HDC hDC; // DC to screen MSG msg; // Message for the PeekMessage() hDC = CreateDC("DISPLAY", NULL, NULL, NULL); rScreen.left = rScreen.top = 0; rScreen.right = GetDeviceCaps(hDC, HORZRES); rScreen.bottom = GetDeviceCaps(hDC, VERTRES); strcpy(szWindowText, "Entire screen"); /* Bring the previous current window to the top of the Z-order. * Wait until this application gets another message -- this allows * the other windows (which were obscured before by the dialog * box) to repaint themselves. */ BringWindowToTop(hWndCurrent); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } /* * Call the DIB API function which captures the screen. This * will automatically allocate enough space for the DIB, fill * in the proper fields in the DIB header, and return us a * pointer to the memory containing the header, colortable, * and bits of the DIB. */ hDib = CopyScreenToDIB(&rScreen); } break; case IDC_PARTIALSCREEN: /* * Copy user-selected portion of screen to DIB */ { RECT rRubberBand; // Region to capture (screen coordinates) /* * Allow user to "rubberband" a section of the screen for * us to capture */ RubberBandScreen(&rRubberBand); strcpy(szWindowText, "User selected portion"); /* * Call the DIB API function which captures the screen. This * will automatically allocate enough space for the DIB, fill * in the proper fields in the DIB header, and return us a * pointer to the memory containing the header, colortable, * and bits of the DIB. */ hDib = CopyScreenToDIB(&rRubberBand); } break; case IDC_SINGLEWINDOW: /* * Allow the user to click on a single window to capture */ { HWND hWndSelect; HWND hWndDesktop; WORD wOption; /* * Call function which lets user select a window */ hWndSelect = SelectWindow(); /* * Check to see that they didn't try to capture desktop window */ hWndDesktop = GetDesktopWindow(); if (hWndSelect == hWndDesktop) { MessageBox(NULL, "Cannot capture Desktop window." " Use 'Entire Screen' option to capture" " the entire screen.", szAppName, MB_ICONEXCLAMATION | MB_OK); hDib = NULL; break; } /* * Check to see that the hWnd is not NULL */ if (!hWndSelect) { MessageBox(NULL, "Cannot capture that window!", szAppName, MB_ICONEXCLAMATION | MB_OK); hDib = NULL; break; } /* * Make sure it's not a hidden window. Hmm, capturing a hidden * window would certainly be a cool trick, wouldn't it? */ if (!IsWindowVisible(hWndSelect)) { MessageBox(NULL, "Window is not visible. Can't capture", szAppName, MB_ICONEXCLAMATION | MB_OK); hDib = NULL; break; } // Move window which was selected to top of Z-order for // the capture, and make it redraw itself SetWindowPos(hWndSelect, NULL, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOSIZE | SWP_NOMOVE); UpdateWindow(hWndSelect); /* * Get the caption */ GetWindowText(hWndSelect, szWindowText, 100); /* * Convert our OPTIONSTRUCT options to DIB API options */ switch (lpOptionStruct->iOptionWindow) { case IDC_CLIENTAREAONLY: wOption = PW_CLIENT; break; case IDC_ENTIREWINDOW: default: wOption = PW_WINDOW; break; } /* * Call the DIB API function which captures the screen. This * will automatically allocate enough space for the DIB, fill * in the proper fields in the DIB header, and return us a * pointer to the memory containing the header, colortable, * and bits of the DIB. */ hDib = CopyWindowToDIB(hWndSelect, wOption); } break; default: /* * Oops, something went wrong */ MessageBox(NULL, "Invalid Return value from Options DialogBox", szAppName, MB_ICONHAND | MB_OK); break; } /* * At this point, if hDib is NULL, then there was an error. We should * have already taken care of informing the user of the error. */ if (!hDib) { GlobalUnlock(hOptionStruct); DestroyDIB(hDib); return; } /* * Now, process the destination information (e.g. to printer or file) */ /* * See if the user checked the checkbox "Send to File" */ if (lpOptionStruct->iOptionDest & OPTION_FILE) { WORD wReturn; FARPROC lpfnDIALOGSMsgProc; // First, call up a modeless dialog box which tells that we are saving // this to a file... if (!hModelessDlg) { lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC)SavingDlgProc, ghInst); hModelessDlg = CreateDialogParam(ghInst, (LPSTR)"Saving", ghWndMain, lpfnDIALOGSMsgProc, ( DWORD)(LPSTR)lpOptionStruct-> szFileName); } /* * Call DIB API function which saves dib to file */ wReturn = SaveDIB(hDib, (LPSTR)lpOptionStruct->szFileName); DestroyWindow(hModelessDlg); hModelessDlg = NULL; if (wReturn) MessageBox(NULL, "Error saving file", szAppName, MB_ICONEXCLAMATION | MB_OK); FreeProcInstance(lpfnDIALOGSMsgProc); } /* * See if the user checked the box "Print to printer" */ if (lpOptionStruct->iOptionDest & OPTION_PRINTER) { WORD wReturn; WORD wOption; WORD wX = 0, wY = 0; switch (lpOptionStruct->iOptionPrint) { case IDC_STRETCHTOPAGE: wOption = PW_STRETCHTOPAGE; break; case IDC_SCALE: wOption = PW_SCALE; wX = lpOptionStruct->iXScale; wY = lpOptionStruct->iYScale; break; default: case IDC_BESTFIT: wOption = PW_BESTFIT; break; } /* * Send the bitmap to the printer, using the specified options */ wReturn = PrintDIB(hDib, wOption, wX, wY, (LPSTR)szWindowText); if (wReturn) { DIBError(wReturn); } } /* * Clean up -- deallocate memory used for DIB */ GlobalUnlock(hOptionStruct); DestroyDIB(hDib); } } /*********************************************************************** * * SelectWindow() * * This function allows the user to select a window on the screen. The * cursor is changed to a custom cursor, then the user clicks on the title * bar of a window to capture, and the handle to this window is returned. * **********************************************************************/ HWND SelectWindow() { HCURSOR hOldCursor; // Handle to old cursor POINT pt; // Stores mouse position on a mouse click HWND hWndClicked; // Window we clicked on MSG msg; /* * Capture all mouse messages */ SetCapture(ghWndMain); /* * Load custom Cursor */ hOldCursor = SetCursor(LoadCursor(ghInst, "SELECT")); /* * Eat mouse messages until a WM_LBUTTONUP is encountered. */ for (;;) { WaitMessage(); if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) { if (msg.message == WM_LBUTTONUP) { /* * Get mouse position */ pt.x = LOWORD(msg.lParam); pt.y = HIWORD(msg.lParam); /* * Convert to screen coordinates */ ClientToScreen(ghWndMain, &pt); /* * Get Window that we clicked on */ hWndClicked = WindowFromPoint(pt); /* * If it's not a valid window, just return NULL */ if (!hWndClicked) { ReleaseCapture(); SetCursor(hOldCursor); return NULL; } break; } } else continue; } ReleaseCapture(); SetCursor(hOldCursor); return (hWndClicked); } /*********************************************************************** * * RubberBandScreen() * * This function allows the user to rubber-band a portion of the screen. * When the left button is released, the rect that the user selected * (in screen coordinates) is returned in lpRect. * **********************************************************************/ void RubberBandScreen(LPRECT lpRect) { POINT pt; // Temporary POINT MSG msg; // Used in our PeekMessage() loop POINT ptOrigin; // Point where the user pressed left mouse button down RECT rcClip; // Current selection HDC hScreenDC; // DC to the screen (so we can draw on it) HCURSOR hOldCursor; // Saves old cursor BOOL bCapturing = FALSE; // TRUE if we are rubber-banding hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL); /* * Make cursor our custom cursor */ hOldCursor = SetCursor(LoadCursor(NULL, IDC_CROSS)); /* * Capture all mouse messages */ SetCapture(ghWndMain); /* Eat mouse messages until a WM_LBUTTONUP is encountered. Meanwhile * continue to draw a rubberbanding rectangle and display it's dimensions */ for (;;) { WaitMessage(); if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) { /* * If the message is a WM_LBUTTONDOWN, begin drawing the * rubber-band box. */ if (msg.message == WM_LBUTTONDOWN) { /* * User pressed left button, initialize selection * Set origin to current mouse position (in window coords) */ ptOrigin.x = LOWORD(msg.lParam); ptOrigin.y = HIWORD(msg.lParam); /* * Convert to screen coordinates */ ClientToScreen(ghWndMain, &ptOrigin); /* * rcClip is the current rectangle that the user * has selected. Since user just pressed left button down, * initialize this rect very small */ rcClip.left = rcClip.right = ptOrigin.x; rcClip.top = rcClip.bottom = ptOrigin.y; NormalizeRect(&rcClip); // Make sure it is a normal rect DrawSelect(hScreenDC, TRUE, &rcClip); // Draw the rubber-band box bCapturing = TRUE; } /* * Any messages that make it into the next statement are mouse * messages, and we are capturing, so let's update the rubber-band * box */ if (bCapturing) { DrawSelect(hScreenDC, FALSE, &rcClip); // erase old rubber-band rcClip.left = ptOrigin.x; // Update rect with new mouse info rcClip.top = ptOrigin.y; pt.x = LOWORD(msg.lParam); pt.y = HIWORD(msg.lParam); /* * Convert to screen coordinates */ ClientToScreen(ghWndMain, &pt); rcClip.right = pt.x; rcClip.bottom = pt.y; NormalizeRect(&rcClip); DrawSelect(hScreenDC, TRUE, &rcClip); // And draw the new rubber-band } // If the message is WM_LBUTTONUP, then we stop the selection // process. if (msg.message == WM_LBUTTONUP) { DrawSelect(hScreenDC, FALSE, &rcClip); // erase rubber-band SetCursor(hOldCursor); break; } } else continue; } ReleaseCapture(); DeleteDC(hScreenDC); /* * Assign rect user selected to lpRect parameter */ CopyRect(lpRect, &rcClip); } /**************************************************************************** * * DrawSelect * * Draws the selected clip rectangle with its dimensions on the DC * This code is taken from DIBVIEW. * ****************************************************************************/ void DrawSelect(HDC hdc, // DC to draw on BOOL fDraw, // TRUE to draw, FALSE to erase LPRECT lprClip) // rect to draw { char sz[80]; DWORD dw; int x, y, len, dx, dy; HDC hdcBits; HBITMAP hbm; RECT rcClip; rcClip = *lprClip; if (!IsRectEmpty(&rcClip)) { /* If a rectangular clip region has been selected, draw it */ PatBlt(hdc, rcClip.left, rcClip.top, rcClip.right - rcClip.left, 1, DSTINVERT); PatBlt(hdc, rcClip.left, rcClip.bottom, 1, -(rcClip.bottom - rcClip.top) , DSTINVERT); PatBlt(hdc, rcClip.right - 1, rcClip.top, 1, rcClip.bottom - rcClip.top, DSTINVERT); PatBlt(hdc, rcClip.right, rcClip.bottom - 1, -(rcClip.right - rcClip.left), 1, DSTINVERT); /* Format the dimensions string ...*/ wsprintf(sz, "%dx%d", rcClip.right - rcClip.left, rcClip.bottom - rcClip.top); len = lstrlen(sz); /* ... and center it in the rectangle */ dw = GetTextExtent(hdc, sz, len); dx = LOWORD (dw); dy = HIWORD (dw); x = (rcClip.right + rcClip.left - dx) / 2; y = (rcClip.bottom + rcClip.top - dy) / 2; hdcBits = CreateCompatibleDC(hdc); SetTextColor(hdcBits, 0xFFFFFFL); SetBkColor(hdcBits, 0x000000L); /* Output the text to the DC */ if (hbm = CreateBitmap(dx, dy, 1, 1, NULL)) { hbm = SelectObject(hdcBits, hbm); ExtTextOut(hdcBits, 0, 0, 0, NULL, sz, len, NULL); BitBlt(hdc, x, y, dx, dy, hdcBits, 0, 0, SRCINVERT); hbm = SelectObject(hdcBits, hbm); DeleteObject(hbm); } DeleteDC(hdcBits); } } /**************************************************************************** * * * FUNCTION : NormalizeRect(RECT *prc) * * * * PURPOSE : If the rectangle coordinates are reversed, swaps them * * * ****************************************************************************/ void PASCAL NormalizeRect(LPRECT prc) { if (prc->right < prc->left) SWAP(prc->right,prc->left); if (prc->bottom < prc->top) SWAP(prc->bottom,prc->top); }