/**************************************************************************\ * * Copyright (c) 1999 Microsoft Corporation * * Module Name: * * warpdemo.cxx * * Abstract: * * Image warping demo program * * Usage: * warpdemo bitmapfile * * Keystrokes: * SPACE - show/hide mesh * r - reset mesh to default * 1 - restore 1-to-1 scale * < - decrease mesh density * > - increase mesh density * f - toggle realtime feedback * * Revision History: * * 01/18/1999 davidx * Created it. * \**************************************************************************/ #include "precomp.hxx" CHAR* programName; // program name HINSTANCE appInstance; // handle to the application instance HWND hwndMain; // handle to application's main window SIZE srcSize; // source bitmap size PVOID srcBmpData; // source bitmap data INT srcStride; // source scanline stride SIZE dstSize; // destination bitmap size PVOID dstBmpData; // destination bitmap data INT dstStride; // destination scanline stride SIZE wndSizeExtra; // extra pixels for window decorations BOOL isDragging = FALSE; // used to handle mouse dragging INT dragRow, dragCol; // the control knob being dragged BOOL clearWarpCache; // is the cached warping result valid? BOOL liveFeedback = FALSE; // realtime feedback while dragging mesh? INT knobSize; // mesh control point knob size #define MIN_KNOB_SIZE 4 #define MAX_KNOB_SIZE 7 #define MIN_MESH_GRID 3 #define MAX_MESH_GRID 32 #define DEFAULT_MESH_GRID 9 INT meshGrids = DEFAULT_MESH_GRID; Mesh* mesh = NULL; BOOL showMesh = TRUE; // // Display an error message dialog and quit // VOID Error( const CHAR* fmt, ... ) { va_list arglist; va_start(arglist, fmt); vfprintf(stderr, fmt, arglist); va_end(arglist); exit(-1); } // // Create a new mesh object // VOID CreateMesh() { mesh = new Mesh(meshGrids, meshGrids); if (mesh == NULL) Error("Couldn't create Mesh object\n"); clearWarpCache = TRUE; } // // Calculate mesh control point knob size based // on current window width and height and also // the number of mesh grids. // VOID CalcKnobSize( INT width, INT height ) { width /= (meshGrids-1); height /= (meshGrids-1); knobSize = min(width, height) / 5; if (knobSize < MIN_KNOB_SIZE) knobSize = MIN_KNOB_SIZE; else if (knobSize > MAX_KNOB_SIZE) knobSize = MAX_KNOB_SIZE; } // // Perform image warping operation based on current mesh configuration // HDC hdcWarp = NULL; HBITMAP hbmpWarp = NULL; VOID DoWarp( INT width, INT height ) { // Uncache any previous warping results if (hdcWarp) DeleteDC(hdcWarp); if (hbmpWarp) DeleteObject(hbmpWarp); // Create offscreen DC to cache warping results dstSize.cx = width; dstSize.cy = height; dstStride = ((width * PIXELSIZE) + 3) & ~3; BITMAPINFOHEADER header = { sizeof(header), dstSize.cx, -dstSize.cy, 1, PIXELSIZE*8, BI_RGB, }; hdcWarp = CreateCompatibleDC(NULL); hbmpWarp = CreateDIBSection( NULL, (BITMAPINFO*) &header, DIB_RGB_COLORS, &dstBmpData, NULL, 0); if (!hdcWarp || !hbmpWarp) Error("Couldn't create DC to cache warping results\n"); SelectObject(hdcWarp, hbmpWarp); // Horizontal pass PVOID tmpBmpData; INT x, y; double* outpos; tmpBmpData = malloc(dstStride*(srcSize.cy + 2)); outpos = (double*) malloc(sizeof(double) * (max(srcSize.cx, srcSize.cy) + 1)); if (!tmpBmpData || !outpos) Error("Could allocate temporary memory for warping\n"); MeshIterator* iterator; iterator = mesh->getYIterator(srcSize.cx, dstSize.cx, srcSize.cy); for (y=0; y < srcSize.cy; y++) { // compute the output position for each iterator->getOutPos(y, outpos); // perform 1D resampling Resample1D( (PBYTE) srcBmpData + y*srcStride, srcSize.cx, (PBYTE) tmpBmpData + y*dstStride, dstSize.cx, PIXELSIZE, outpos); } delete iterator; // Vertical pass iterator = mesh->getXIterator(srcSize.cy, dstSize.cy, dstSize.cx); for (x=0; x < dstSize.cx; x++) { // compute the output position for each iterator->getOutPos(x, outpos); // perform 1D resampling Resample1D( (PBYTE) tmpBmpData + PIXELSIZE*x, srcSize.cy, (PBYTE) dstBmpData + PIXELSIZE*x, dstSize.cy, dstStride, outpos); } delete iterator; free(tmpBmpData); free(outpos); } // // Draw mesh // #define MESHCOLOR RGB(255, 0, 0) VOID DrawMesh( HDC hdc ) { static HPEN meshPen = NULL; static HBRUSH meshBrush = NULL; mesh->setDstSize(dstSize.cx, dstSize.cy); // Create the pen to draw the mesh, if necessary if (meshPen == NULL) meshPen = CreatePen(PS_SOLID, 1, MESHCOLOR); SelectObject(hdc, meshPen); // Draw horizontal meshes INT i, j, rows, cols, pointCount; POINT* points; rows = mesh->getGridRows(); for (i=0; i < rows; i++) { points = mesh->getMeshRowBeziers(i, &pointCount); PolyBezier(hdc, points, pointCount); } // Draw vertical meshes cols = mesh->getGridColumns(); for (i=0; i < cols; i++) { points = mesh->getMeshColumnBeziers(i, &pointCount); PolyBezier(hdc, points, pointCount); } // Draw knobs // Create the brush to draw the mesh if necessary if (meshBrush == NULL) meshBrush = CreateSolidBrush(MESHCOLOR); for (i=0; i < rows; i++) { points = mesh->getMeshRowPoints(i, &pointCount); for (j=0; j < cols; j++) { RECT rect; rect.left = points[j].x - knobSize/2; rect.top = points[j].y - knobSize/2; rect.right = rect.left + knobSize; rect.bottom = rect.top + knobSize; FillRect(hdc, &rect, meshBrush); } } } // // Handle window repaint event // VOID DoPaint( HWND hwnd ) { HDC hdc; PAINTSTRUCT ps; RECT rect; INT width, height; // Determine if we need to perform warping operation GetClientRect(hwnd, &rect); width = rect.right; height = rect.bottom; if (clearWarpCache || dstSize.cx != width || dstSize.cy != height) { CalcKnobSize(width, height); clearWarpCache = FALSE; DoWarp(width, height); } hdc = BeginPaint(hwnd, &ps); if (showMesh) { // Draw to offscreen DC to reduce flashing HDC hdcMem; HBITMAP hbmp; hdcMem = CreateCompatibleDC(hdc); hbmp = CreateCompatibleBitmap(hdc, width, height); SelectObject(hdcMem, hbmp); BitBlt(hdcMem, 0, 0, width, height, hdcWarp, 0, 0, SRCCOPY); DrawMesh(hdcMem); // Blt from offscreen memory to window BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY); DeleteDC(hdcMem); DeleteObject(hbmp); } else { // Blt cached warping result to window BitBlt(hdc, 0, 0, width, height, hdcWarp, 0, 0, SRCCOPY); } EndPaint(hwnd, &ps); } // // Handle WM_SIZING message // BOOL DoWindowSizing( HWND hwnd, RECT* rect, INT side ) { INT w = rect->right - rect->left - wndSizeExtra.cx; INT h = rect->bottom - rect->top - wndSizeExtra.cy; if (w >= srcSize.cx && h >= srcSize.cy) return FALSE; // Window width is too small if (w < srcSize.cx) { INT dx = srcSize.cx + wndSizeExtra.cx; switch (side) { case WMSZ_LEFT: case WMSZ_TOPLEFT: case WMSZ_BOTTOMLEFT: rect->left = rect->right - dx; break; default: rect->right = rect->left + dx; break; } } // Window height is too small if (h < srcSize.cy) { INT dy = srcSize.cy + wndSizeExtra.cy; switch (side) { case WMSZ_TOP: case WMSZ_TOPLEFT: case WMSZ_TOPRIGHT: rect->top = rect->bottom - dy; break; default: rect->bottom = rect->top + dy; break; } } return TRUE; } // // Handle left mouse-down event // VOID DoMouseDown( HWND hwnd, INT x, INT y ) { // Figure out if the click happened in a mesh control knob INT i, j, rows, cols; POINT pt; RECT rect; GetClientRect(hwnd, &rect); mesh->setDstSize(rect.right, rect.bottom); rows = mesh->getGridRows(); cols = mesh->getGridColumns(); for (i=0; i < rows; i++) for (j=0; j < cols; j++) { mesh->getMeshPoint(i, j, &pt); pt.x -= knobSize/2; pt.y -= knobSize/2; if (x >= pt.x && x < pt.x+knobSize && y >= pt.y && y < pt.y+knobSize) { dragRow = i; dragCol = j; SetCapture(hwnd); isDragging = TRUE; return; } } } // // Handle mouse-move event // VOID DoMouseMove( HWND hwnd, INT x, INT y ) { // We assume isDragging is true here. RECT rect; INT w, h; GetClientRect(hwnd, &rect); w = rect.right; h = rect.bottom; if (x < 0 || x >= w || y < 0 || y >= h) return; mesh->setDstSize(w, h); if (mesh->setMeshPoint(dragRow, dragCol, x, y)) { if (liveFeedback) clearWarpCache = TRUE; InvalidateRect(hwnd, NULL, FALSE); } } // // Handle menu command // VOID DoCommand( HWND hwnd, INT command ) { switch (command) { case IDC_RESETMESH: mesh->initMesh(); clearWarpCache = TRUE; break; case IDC_TOGGLEMESH: showMesh = !showMesh; break; case IDC_SHRINKTOFIT: SetWindowPos( hwnd, NULL, 0, 0, srcSize.cx + wndSizeExtra.cx, srcSize.cy + wndSizeExtra.cy, SWP_NOOWNERZORDER|SWP_NOMOVE); break; case IDC_DENSEMESH: if (meshGrids >= MAX_MESH_GRID) return; meshGrids++; CreateMesh(); showMesh = TRUE; break; case IDC_SPARSEMESH: if (meshGrids <= MIN_MESH_GRID) return; meshGrids--; CreateMesh(); showMesh = TRUE; break; case IDC_LIVEFEEDBACK: liveFeedback = !liveFeedback; return; default: return; } InvalidateRect(hwnd, NULL, FALSE); } // // Handle popup menu // VOID DoPopupMenu( HWND hwnd, INT x, INT y ) { HMENU menu; DWORD result; POINT pt; GetCursorPos(&pt); menu = LoadMenu(appInstance, MAKEINTRESOURCE(IDM_MAINMENU)); result = TrackPopupMenu( GetSubMenu(menu, 0), TPM_CENTERALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL); if (result == 0) return; DoCommand(hwnd, LOWORD(result)); } // // Window callback procedure // LRESULT CALLBACK MyWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { INT x, y; switch (uMsg) { case WM_PAINT: DoPaint(hwnd); break; case WM_LBUTTONDOWN: if (showMesh) { x = (SHORT) LOWORD(lParam); y = (SHORT) HIWORD(lParam); DoMouseDown(hwnd, x, y); } break; case WM_LBUTTONUP: if (isDragging) { ReleaseCapture(); isDragging = FALSE; clearWarpCache = TRUE; InvalidateRect(hwnd, NULL, FALSE); } break; case WM_MOUSEMOVE: if (isDragging) { x = (SHORT) LOWORD(lParam); y = (SHORT) HIWORD(lParam); DoMouseMove(hwnd, x, y); } break; case WM_SIZING: if (DoWindowSizing(hwnd, (RECT*) lParam, wParam)) return TRUE; else return DefWindowProc(hwnd, uMsg, wParam, lParam); case WM_SIZE: InvalidateRect(hwnd, NULL, FALSE); break; case WM_CHAR: switch ((CHAR) wParam) { case 'r': // reset DoCommand(hwnd, IDC_RESETMESH); break; case ' ': // show/hide mesh DoCommand(hwnd, IDC_TOGGLEMESH); break; case '1': // restore 1-to-1 scale DoCommand(hwnd, IDC_SHRINKTOFIT); break; case '<': // decrease mesh density DoCommand(hwnd, IDC_SPARSEMESH); break; case '>': // increase mesh density DoCommand(hwnd, IDC_DENSEMESH); break; case 'f': // toggle live feedback DoCommand(hwnd, IDC_LIVEFEEDBACK); break; } break; case WM_RBUTTONDOWN: x = (SHORT) LOWORD(lParam); y = (SHORT) HIWORD(lParam); DoPopupMenu(hwnd, x, y); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } // // Create main application window // VOID CreateMainWindow( VOID ) #define MYWNDCLASSNAME "WarpDemo" { // // Register window class if necessary // static BOOL wndclassRegistered = FALSE; if (!wndclassRegistered) { WNDCLASS wndClass = { 0, MyWindowProc, 0, 0, appInstance, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW), NULL, NULL, MYWNDCLASSNAME }; RegisterClass(&wndClass); wndclassRegistered = TRUE; } wndSizeExtra.cx = 2*GetSystemMetrics(SM_CXSIZEFRAME); wndSizeExtra.cy = 2*GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION); hwndMain = CreateWindow( MYWNDCLASSNAME, MYWNDCLASSNAME, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, srcSize.cx + wndSizeExtra.cx, srcSize.cy + wndSizeExtra.cy, NULL, NULL, appInstance, NULL); } // // Map a file into process memory space // PVOID MapFileIntoMemory( PCSTR filename, DWORD* size ) { HANDLE filehandle, filemap; PVOID fileview = NULL; // // Open a handle to the specified file // filehandle = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL||FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (filehandle == INVALID_HANDLE_VALUE) return NULL; // // Obtain the file size // *size = GetFileSize(filehandle, NULL); if (*size == 0xFFFFFFFF) { CloseHandle(filehandle); return NULL; } // // Map the file into memory // filemap = CreateFileMapping(filehandle, NULL, PAGE_READONLY, 0, 0, NULL); if (filemap != NULL) { fileview = MapViewOfFile(filemap, FILE_MAP_READ, 0, 0, 0); CloseHandle(filemap); } CloseHandle(filehandle); return fileview; } // // Load source bitmap file // VOID LoadBitmapFile( PCSTR filename ) { BITMAPFILEHEADER* bmpfile; BITMAPINFO* bmpinfo; PBYTE bmpdata; HBITMAP dibSection = NULL; DWORD filesize; PVOID fileview = NULL; HDC hdc = NULL; BOOL success = FALSE; __try { // // Map the bitmap file into memory // fileview = MapFileIntoMemory(filename, &filesize); if (fileview == NULL) __leave; bmpfile = (BITMAPFILEHEADER *) fileview; bmpinfo = (BITMAPINFO *) ((PBYTE) fileview + sizeof(BITMAPFILEHEADER)); bmpdata = (PBYTE) fileview + bmpfile->bfOffBits; // // Check bitmap file header information // if (bmpfile->bfType != 0x4D42 || // 'BM' bmpfile->bfSize > filesize || bmpfile->bfOffBits >= bmpfile->bfSize) { __leave; } // // Allocate memory for source bitmap // srcSize.cx = bmpinfo->bmiHeader.biWidth; srcSize.cy = abs(bmpinfo->bmiHeader.biHeight); srcStride = ((srcSize.cx * PIXELSIZE) + 3) & ~3; BITMAPINFOHEADER header = { sizeof(header), srcSize.cx, -srcSize.cy, 1, PIXELSIZE*8, BI_RGB, }; dibSection = CreateDIBSection( NULL, (BITMAPINFO*) &header, DIB_RGB_COLORS, &srcBmpData, NULL, 0); if (!dibSection) __leave; // // Blt from the bitmap file to the DIB section // HBITMAP hbmp; hdc = CreateCompatibleDC(NULL); hbmp = (HBITMAP) SelectObject(hdc, dibSection); StretchDIBits( hdc, 0, 0, srcSize.cx, srcSize.cy, 0, 0, srcSize.cx, srcSize.cy, bmpdata, bmpinfo, DIB_RGB_COLORS, SRCCOPY); SelectObject(hdc, hbmp); success = TRUE; } __except(EXCEPTION_EXECUTE_HANDLER) { // AV while reading bitmap file } if (hdc) DeleteDC(hdc); if (fileview) UnmapViewOfFile(fileview); if (!success) Error("Failed to read source bitmap\n"); } // // Main program entrypoint // INT _cdecl main( INT argc, CHAR **argv ) { programName = *argv++; argc--; appInstance = GetModuleHandle(NULL); // Load source bitmap file if (argc != 1) Error("usage: %s bitmapfile\n", programName); LoadBitmapFile(*argv); // Initialize mesh configuration CreateMesh(); // Create the main application window CreateMainWindow(); // Main message loop MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }