#include #include // Status: // All timings work with SDK and QT versions. // // Todo: // Test with framelock to prevent tearing // How long does screen shot take? // Enable/disable notch filter // Add software flicker filter // Add full screen capture // Can it be done without a second thread? // Add rate control // Get QT version working with rowbytes someday // Done: // Verify that all 4 capture sizes work for both QT and SDK (2/10/99) // Clean up final rubberband on exit-by-escape // Make width and height of menus nicer // Determine whether screen is 16 or 32 bit for pixel format #include "scr2vid.h" #include "debug.h" #include "v.h" // header defining video interface #ifdef SDKIO #include "sdkio.c" // Include SDK-version video I/O #define WINDOWNAME "SDK Screen To Video" #else // QTIO #include "qtio.c" // Include QT-version video I/O #define WINDOWNAME "QT Screen To Video" #endif int _dprint = 1; #define NTSC_HEIGHT 480 #define NTSC_WIDTH 640 #define NTSC_WIDTH_NSQ 720 #define PAL_HEIGHT 576 #define PAL_WIDTH 768 #define PAL_WIDTH_NSQ 720 int jcurrent = 0; // 1st jack in list is default char **jlist; int jcount; RECT grabrect; void *next_image (void); CHAR szAppName [] = "Vid Out"; // Aplication name. CHAR szWindowName[] = WINDOWNAME; // Application name. HINSTANCE ghInst; // Instance handle. HWND ghwndApp; // Main window handle. HANDLE ghaccelTable; // Main accelerator table. INT gcxScreenMax; // Width of the screen (less 1). INT gcyScreenMax; // Height of the screen (less 1). BOOL gfTracking = FALSE; // TRUE if tracking is in progress. POINT gptZoom = {0, 0}; // Upper left point of zoomed area. // Source width and height, what gets grabbed from the // screen. QT currently doesn't handle rowbytes, so always // set these to the width and height of the image going out // video. int srcw, srch; // Video width and height, what goes out the video port. // int iHeight, iWidth; WORD wDefaultSize = MENU_SIZE_NTSC; HDC hdcMemory, hdcScreen; int gflags = 0; #define DRAWBOX 1 // draw the video bounding box #define FFILTER 2 // enable the software flicker filter BITMAPINFO *globpbmi; #define BUFCOUNT 4 int globbufcnt = BUFCOUNT; int globcurbuf = 0; void destroy_buffers(BITMAPINFO *); BITMAPINFO *make_buffers(void); int initialize_buffers(void); int uninitialize_buffers(void); int getpixelsize(void); void filt(void *in, void *out, int w, int h); typedef struct bitmap_s { HBITMAP bm; // GDI bitmap void *ptr; // Pointer to memory data void *hdr; // Video I/O header (gworld for QT) } bitmap_t; bitmap_t bitmaps[BUFCOUNT]; /************************************************************************ * WinMain * * Main entry point for the application. * ************************************************************************/ int WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; if (!InitInstance(hInst, nCmdShow)) return FALSE; /* * Polling messages from event queue */ while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(ghwndApp, ghaccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } /************************************************************************ * InitInstance * * Instance initialization for the app. * ************************************************************************/ BOOL InitInstance (HINSTANCE hInst, INT cmdShow) { WNDCLASS wc; DWORD flStyle; RECT rc; ghInst = hInst; /* * Register a class for the main application window. */ wc.hCursor = LoadCursor (NULL, IDC_ARROW); wc.hIcon = LoadIcon (hInst, "scr2vid"); wc.lpszMenuName = MAKEINTRESOURCE(IDMENU_MAIN); wc.lpszClassName = szAppName; wc.hbrBackground = GetStockObject (BLACK_BRUSH); wc.hInstance = hInst; wc.style = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW; wc.lpfnWndProc = (WNDPROC)AppWndProc; wc.cbWndExtra = 0; wc.cbClsExtra = 0; if (!RegisterClass(&wc)) return FALSE; gcxScreenMax = GetSystemMetrics(SM_CXSCREEN) - 1; gcyScreenMax = GetSystemMetrics(SM_CYSCREEN) - 1; flStyle = WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX; // Initial window size SetRect (&rc, 0, 0, 220, 70); AdjustWindowRect (&rc, flStyle, TRUE); ghwndApp = CreateWindow(szAppName, szWindowName, flStyle, CW_USEDEFAULT, 0, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInst, NULL); if (!ghwndApp) return FALSE; ShowWindow(ghwndApp, cmdShow); return TRUE; } /************************************************************************ * AppWndProc * * Main window proc for the scr2vid utility. * * Arguments: * Standard window proc args. * * History: * ************************************************************************/ LONG APIENTRY AppWndProc (HWND hwnd, UINT msg, WPARAM wParam, LONG lParam) { PAINTSTRUCT ps; HMENU hMainMenu, hVideoMenu; int jack; volatile static threadargs_t ta; DWORD tid; switch (msg) { case WM_CREATE: hdcScreen = GetDC (NULL); hdcMemory = CreateCompatibleDC (NULL); v_init(); hMainMenu = GetMenu (hwnd); hVideoMenu = GetSubMenu (hMainMenu, 3); // video submenu if (v_make_jacklist(&jlist, &jcount)) return -1; for (jack = 0; jack < jcount; jack++) { AppendMenu (hVideoMenu, MF_STRING | MF_UNCHECKED, MENU_VIDEO_0 + jack, jlist[jack]); } DeleteMenu (hVideoMenu, 0, MF_BYPOSITION); SendMessage (hwnd, WM_COMMAND, MAKEWPARAM (MENU_VIDEO_0 + jcurrent, 0), 0L); SendMessage (hwnd, WM_COMMAND, MAKEWPARAM (wDefaultSize, 0), 0L); CheckMenuItem (hwnd, MENU_OPT_BOX, gflags & DRAWBOX? MF_CHECKED : MF_UNCHECKED); CheckMenuItem (hwnd, MENU_OPT_FILT, gflags & FFILTER? MF_CHECKED : MF_UNCHECKED); break; case WM_PAINT: BeginPaint (hwnd, &ps); EndPaint (hwnd, &ps); break; case WM_LBUTTONDOWN: // Begin the transfer on Left Button // if (gfTracking) break; globpbmi = make_buffers(); if (!globpbmi) return -1; if (initialize_buffers()) return -1; DrawZoomRect (); SetCapture (hwnd); if (v_start(jcurrent, iWidth, iHeight)) { PRINT1("jcurrent %d\n", jcurrent); PRINT2("width %d, height %d\n", iWidth, iHeight); MessageBox (hwnd, "Cannot initialize video.", "Error!", MB_OK | MB_ICONERROR); return -1; } gfTracking = TRUE; // Initialize thread args & start the video thread // ta.killthread = 0; ta.nextbuf = next_image; CreateThread (NULL, 0, v_thread, (void *)&ta, 0, &tid); break; case WM_LBUTTONUP: case WM_RBUTTONUP: break; case WM_RBUTTONDOWN: // End the transfer on Right Button // if (!gfTracking) break; if (gfTracking) { DrawZoomRect (); ReleaseCapture (); gfTracking = FALSE; } // Kill the video transfer thread ta.killthread = 1; // kill off the thread while (ta.killthread == 1) // wait for thread to exit Sleep(1); // Stop the video transfer // v_stop(); // Reset the video message queue // v_reset(); uninitialize_buffers(); destroy_buffers(globpbmi); globpbmi = 0; break; case WM_KEYDOWN: // Move the capture rectangle on Arrow Keys if (!gfTracking) break; DrawZoomRect (); switch (wParam) { case VK_ESCAPE: SendMessage(hwnd, WM_RBUTTONDOWN, MAKEWPARAM (VK_ESCAPE, 0), 0L); break; case VK_UP: case VK_DOWN: case VK_LEFT: case VK_RIGHT: MoveView((INT)wParam, GetKeyState(VK_SHIFT) & 0x8000, GetKeyState(VK_CONTROL) & 0x8000); break; } DrawZoomRect (); break; case WM_COMMAND: switch (LOWORD(wParam)) { case MENU_FILE_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0); break; case MENU_OPT_BOX: if (gflags & DRAWBOX) gflags &= ~DRAWBOX; else gflags |= DRAWBOX; CheckMenuItem (GetMenu(hwnd), MENU_OPT_BOX, (gflags & DRAWBOX) ? MF_CHECKED : MF_UNCHECKED); break; case MENU_OPT_FILT: if (gflags & FFILTER) gflags &= ~FFILTER; else gflags |= FFILTER; CheckMenuItem (GetMenu(hwnd), MENU_OPT_FILT, (gflags & FFILTER) ? MF_CHECKED : MF_UNCHECKED); break; case MENU_SIZE_NTSC: case MENU_SIZE_NTSC_NSQ: case MENU_SIZE_PAL: case MENU_SIZE_PAL_NSQ: switch(LOWORD(wParam)) { case MENU_SIZE_NTSC: iHeight = NTSC_HEIGHT; iWidth = NTSC_WIDTH; break; case MENU_SIZE_NTSC_NSQ: iHeight = NTSC_HEIGHT; iWidth = NTSC_WIDTH_NSQ; break; case MENU_SIZE_PAL: iHeight = PAL_HEIGHT; iWidth = PAL_WIDTH; break; case MENU_SIZE_PAL_NSQ: iHeight = PAL_HEIGHT; iWidth = PAL_WIDTH_NSQ; break; } //#define QTTEST 1 #ifdef QTTEST srcw = 800; srch = 600; #else // !QTTEST srcw = iWidth; srch = iHeight; #endif // !QTTEST CheckMenuRadioItem (GetMenu (hwnd), MENU_SIZE_NTSC, MENU_SIZE_PAL_NSQ, LOWORD (wParam), MF_BYCOMMAND); break; case MENU_HELP_ABOUT: DialogBox (ghInst, (LPSTR) MAKEINTRESOURCE (DID_ABOUT), hwnd, (WNDPROC) AboutDlgProc); break; default: // Since the menu can only be accessed with the // mouse, and the mouse is occupied during a // capture, then nothing must be capturing whenever // the menu is accessed. if (LOWORD(wParam) >= MENU_VIDEO_0 && LOWORD(wParam) < MENU_VIDEO_0 + jcount) { jcurrent = LOWORD(wParam) - MENU_VIDEO_0; CheckMenuRadioItem (GetMenu (hwnd), MENU_VIDEO_0, MENU_VIDEO_0 + jcount, LOWORD (wParam), MF_BYCOMMAND); } break; } break; // case WM_COMMAND case WM_CLOSE: if (gfTracking) // transfer should be stopped first break; ReleaseDC (NULL, hdcScreen); DeleteDC (hdcMemory); v_done(); v_unmake_jacklist(jlist, jcount); PostQuitMessage (0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0L; } /************************************************************************ * next_image * * grabs the next image from the screen * * Arguments: * * History: * ************************************************************************/ void * next_image (void) { INT x, y; HBITMAP hbm; void *ptr, *hdr; HDC hdc; static HBITMAP filthbm = 0; static void *filtptr = 0; hdc = GetDC(ghwndApp); if ((gflags & FFILTER) && !filthbm) { HDC hdcScreen = GetDC(NULL); hbm = CreateDIBSection (hdcScreen, globpbmi, DIB_RGB_COLORS, &ptr, NULL, 0); if (!hbm) { gflags &= ~FFILTER; SendMessage (ghwndApp, WM_COMMAND, MAKEWPARAM (MENU_OPT_FILT, 0), 0L); } else { filthbm = hbm; filtptr = ptr; } } hbm = bitmaps[globcurbuf].bm; ptr = bitmaps[globcurbuf].ptr; hdr = bitmaps[globcurbuf].hdr; // Prepare our surface for drawing if (gflags & FFILTER) SelectObject (hdcMemory, filthbm); else SelectObject (hdcMemory, hbm); // Copy the window contents to our memory surface BitBlt (hdcMemory, 0, 0, globpbmi->bmiHeader.biWidth, -globpbmi->bmiHeader.biHeight, hdcScreen, grabrect.left, grabrect.top, SRCCOPY); ReleaseDC(ghwndApp, hdc); if (gflags & FFILTER) filt(filtptr, ptr, globpbmi->bmiHeader.biWidth, globpbmi->bmiHeader.biHeight); if (++globcurbuf >= globbufcnt) globcurbuf = 0; return hdr; } /************************************************************************ * MoveView * * This function moves the current view around. * * Arguments: * INT nDirectionCode - Direction to move. Must be VK_UP, VK_DOWN, * VK_LEFT or VK_RIGHT. * BOOL fFast - TRUE if the move should jump a larger increment. * If FALSE, the move is just one pixel. * BOOL fPeg - If TRUE, the view will be pegged to the screen * boundary in the specified direction. This overides * the fFast parameter. * * History: * ************************************************************************/ void MoveView(INT nDirectionCode, BOOL fFast, BOOL fPeg) { int delta; delta = fFast ? FASTDELTA : 1; switch (nDirectionCode) { case VK_UP: if (fPeg) gptZoom.y = 0; else gptZoom.y -= delta; gptZoom.y = BOUND(gptZoom.y, 0, gcyScreenMax - iHeight); break; case VK_DOWN: if (fPeg) gptZoom.y = gcyScreenMax - iHeight; else gptZoom.y += delta; gptZoom.y = BOUND(gptZoom.y, 0, gcyScreenMax - iHeight); break; case VK_LEFT: if (fPeg) gptZoom.x = 0; else gptZoom.x -= delta; gptZoom.x = BOUND(gptZoom.x, 0, gcxScreenMax - iWidth); break; case VK_RIGHT: if (fPeg) gptZoom.x = gcxScreenMax - iWidth; else gptZoom.x += delta; gptZoom.x = BOUND(gptZoom.x, 0, gcxScreenMax - iWidth); break; } } /************************************************************************ * DrawZoomRect * * This function draws the tracking rectangle. The size and shape of * the rectangle will be proportional to the size and shape of the * app's client, and will be affected by the zoom factor as well. * * History: * ************************************************************************/ VOID DrawZoomRect(VOID) { HDC hdc; RECT rc; rc.left = gptZoom.x; rc.top = gptZoom.y; rc.right = rc.left + iWidth; // -1 ? rc.bottom = rc.top + iHeight; grabrect = rc; InflateRect (&rc, 1, 1); // Don't update rubberband box on screen if (!(gflags & DRAWBOX)) return; hdc = GetDC(NULL); PatBlt (hdc, rc.left, rc.top, rc.right-rc.left, 1, DSTINVERT); PatBlt (hdc, rc.left, rc.bottom, 1, -(rc.bottom-rc.top), DSTINVERT); PatBlt (hdc, rc.right-1, rc.top, 1, rc.bottom-rc.top, DSTINVERT); PatBlt (hdc, rc.right, rc.bottom-1, -(rc.right-rc.left), 1, DSTINVERT); ReleaseDC(NULL, hdc); } /************************************************************************ * AboutDlgProc * * This is the About Box dialog procedure. * * History: * ************************************************************************/ BOOL APIENTRY AboutDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LONG lParam) { switch (msg) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: EndDialog(hwnd, IDOK); return TRUE; default: return FALSE; } } // // Begin: Create capture buffers and BITMAPINFO // // // Filter input image to output to reduce screen flicker // // My first attempt at a flicker filter was WAY too slow. // Just dummy it out now. // void filt(void *in, void *out, int w, int h) { if (in == out) return; if (h < 0) h = -h; // 16 bit is RBGA5551, 32 bit is ABGR // Skip the filter for now if (getpixelsize() == 16) { memcpy (out, in, w * h * 2); return; } if (getpixelsize() == 32) { memcpy (out, in, w * h * 4); return; } return; } int getpixelsize(void) { static psize = 0; // Never changes once it's set by the application // if (!psize) { DEVMODE devmode; if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode)) { psize = devmode.dmBitsPerPel; } if (psize == 15) psize = 16; else if (psize != 32) { psize = 16; } } return psize; } BITMAPINFO * make_header(void) { int psize; unsigned long *masks; LPBITMAPINFOHEADER lpbi; BITMAPINFO *pbmi; pbmi = malloc (sizeof(BITMAPINFOHEADER) + 4 * 4); if (!pbmi) return 0; // Initialize to 0's ZeroMemory(pbmi, sizeof(BITMAPINFOHEADER) + 4 * 4); psize = getpixelsize(); // Initialize the header pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = srcw; pbmi->bmiHeader.biHeight = -srch; // inverted DIB pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = psize; pbmi->bmiHeader.biCompression = BI_BITFIELDS; pbmi->bmiHeader.biClrUsed = 0; pbmi->bmiHeader.biClrImportant = 0; lpbi = (LPBITMAPINFOHEADER)pbmi; lpbi->biSizeImage = ((((lpbi->biWidth * (DWORD)lpbi->biBitCount) + 31) & ~31) >> 3) * -lpbi->biHeight; // inverted DIB masks = (unsigned long *)&lpbi[1]; if (psize == 32) { // Set up the bitfields masks[0] = 0xff000000; // red masks[1] = 0x00ff0000; // green masks[2] = 0x0000ff00; // blue masks[3] = 0x00000000; // alpha } else { // Set up the bitfields masks[0] = 0x0000f800; // red masks[1] = 0x000007c0; // green masks[2] = 0x0000003e; // blue masks[3] = 0x00000000; // alpha } return pbmi; } void destroy_header(BITMAPINFO *pbmi) { if (pbmi) free(pbmi); } BITMAPINFO * make_buffers(void) { HDC hdcScreen = GetDC(NULL); BITMAPINFO *pbmi; int ix; pbmi = make_header(); if (!pbmi) return 0; for (ix = 0; ix < globbufcnt; ++ix) { HBITMAP hbm; LONG *ptr; hbm = CreateDIBSection (hdcScreen, pbmi, DIB_RGB_COLORS, &ptr, NULL, 0); if (!hbm) return 0; bitmaps[ix].bm = hbm; bitmaps[ix].ptr = ptr; } return pbmi; } void destroy_buffers(BITMAPINFO *pbmi) { int ix; destroy_header(pbmi); for (ix = 0; ix < globbufcnt; ++ix) { DeleteObject (bitmaps[ix].bm); bitmaps[ix].bm = 0; bitmaps[ix].ptr = 0; bitmaps[ix].hdr = 0; } globcurbuf = 0; } int initialize_buffers(void) { int ix; int mul = (getpixelsize() == 16 ? 2 : 4); for (ix = 0; ix < globbufcnt; ++ix) { if (v_make_header(bitmaps[ix].ptr, &bitmaps[ix].hdr, iWidth, iHeight, srcw * mul, getpixelsize())) { return 1; } } return 0; } int uninitialize_buffers(void) { int ix; for (ix = 0; ix < globbufcnt; ++ix) { if (v_unmake_header(bitmaps[ix].hdr)) return 1; } return 0; } // // End: Create capture buffers and BITMAPINFO //