/**************************************************************************** * * capdib.c * * DIB processing module. * * Microsoft Video for Windows Sample Capture Class * * Copyright (c) 1992, 1993 Microsoft Corporation. All Rights Reserved. * * You have a royalty-free right to use, modify, reproduce and * distribute the Sample Files (and/or any modified version) in * any way you find useful, provided that you agree that * Microsoft has no warranty obligations or liability for any * Sample Application Files which are modified. * ***************************************************************************/ #include #include #include #include #include #include "avicap.h" #include "avicapi.h" // // Initialize a DIB to the default format of 160x120x8, BI_RGB // void SetDefaultCaptureFormat (LPBITMAPINFOHEADER lpbih) { lpbih->biSize = sizeof (BITMAPINFOHEADER); lpbih->biWidth = 160; lpbih->biHeight = 120; lpbih->biBitCount = 8; lpbih->biPlanes = 1; lpbih->biCompression = BI_RGB; lpbih->biSizeImage = DIBWIDTHBYTES (*lpbih) * lpbih->biHeight; lpbih->biXPelsPerMeter = 0; lpbih->biYPelsPerMeter = 0; lpbih->biClrUsed = 256; lpbih->biClrImportant = 0; } // // Whenever we get a new format from the driver, OR // start using a new palette, we must reallocate // our global BITMAPINFOHEADER. This allows JPEG // quantization tables to be tacked onto the BITMAPINFO // or any other format specific stuff. The color table // is always offset biSize from the start of the BITMAPINFO. // Returns: 0 on success, or DV_ERR_... code // DWORD AllocNewGlobalBitmapInfo (LPCAPSTREAM lpcs, LPBITMAPINFOHEADER lpbi) { DWORD dwSize; dwSize = lpbi->biSize + 256 * sizeof (RGBQUAD); // The 256 entry above is HARDWIRED ON PURPOSE // If biClrUsed was used instead, we would have to realloc // whenever a palette is pasted (during DibNewPalette())!!! if (lpcs->lpBitsInfo) lpcs->lpBitsInfo = (LPBITMAPINFO) GlobalReAllocPtr (lpcs->lpBitsInfo, dwSize, GHND); else lpcs->lpBitsInfo = (LPBITMAPINFO) GlobalAllocPtr (GHND, dwSize); if (!lpcs->lpBitsInfo) return (DV_ERR_NOMEM); // Copy over the BITMAPINFOHEADER hmemcpy ((HPSTR)lpcs->lpBitsInfo, (HPSTR)lpbi, lpbi->biSize); return DV_ERR_OK; } // // Whenever we get a new format from the driver // allocate a new global bitspace. This bitspace is used // in preview mode and single frame capture. // Returns: 0 on success, or DV_ERR_... code // DWORD AllocNewBitSpace (LPCAPSTREAM lpcs, LPBITMAPINFOHEADER lpbih) { DWORD dwSize; dwSize = lpbih->biSizeImage; if (lpcs->lpBits) lpcs->lpBits = GlobalReAllocPtr (lpcs->lpBits, dwSize, GHND); else lpcs->lpBits = GlobalAllocPtr (GHND, dwSize); if (!lpcs->lpBits) return (DV_ERR_NOMEM); return DV_ERR_OK; } // // Dib Inititialization code // Returns: 0 on success, or DV_ERR_... code // DWORD DibInit (LPCAPSTREAM lpcs) { BITMAPINFOHEADER bmih; SetDefaultCaptureFormat (&bmih); return ((WORD) AllocNewGlobalBitmapInfo (lpcs, &bmih)); } // // Fini code to free all bitmap resources // void DibFini (LPCAPSTREAM lpcs) { if (lpcs->lpBits) { GlobalFreePtr (lpcs->lpBits); lpcs->lpBits = NULL; } if (lpcs->lpBitsInfo) { GlobalFreePtr (lpcs->lpBitsInfo); lpcs->lpBitsInfo = NULL; } lpcs->dxBits = 0; lpcs->dyBits = 0; } // // Send a format to the driver. // Whenever we do a format change, send the driver the // Source and destination rects. // Returns: 0 on success, or DV_ERR_... code // DWORD SendDriverFormat (LPCAPSTREAM lpcs, LPBITMAPINFOHEADER lpbih, DWORD dwInfoHeaderSize) { RECT rc; DWORD dwError = DV_ERR_NOTSUPPORTED; rc.left = rc.top = 0; rc.right = (int) lpbih->biWidth; rc.bottom = (int) lpbih->biHeight; if (dwError = videoConfigure(lpcs->hVideoIn, DVM_FORMAT, VIDEO_CONFIGURE_SET, NULL, (LPBITMAPINFOHEADER)lpbih, dwInfoHeaderSize, NULL, NULL ) ) { return dwError; } else { // Set the ExternalIn Destination rectangle to the same size videoMessage (lpcs->hVideoCapture, DVM_DST_RECT, (DWORD) (LPVOID)&rc, VIDEO_CONFIGURE_SET); // Set the VideoIn Source Rectangle to the same size videoMessage (lpcs->hVideoIn, DVM_SRC_RECT, (DWORD) (LPVOID)&rc, VIDEO_CONFIGURE_SET); // Set the VideoIn Destination Rectangle to the same size videoMessage (lpcs->hVideoIn, DVM_DST_RECT, (DWORD) (LPVOID)&rc, VIDEO_CONFIGURE_SET); } return dwError; } // // Given a DIB, see if the driver likes it, then // allocate the global BITMAPINFOHEADER and bitspace. // // DWORD SetFormatFromDIB (LPCAPSTREAM lpcs, LPBITMAPINFOHEADER lpbih) { DWORD dwError; // Fill optional fields in the DIB header if (lpbih->biSizeImage == 0) lpbih->biSizeImage = DIBWIDTHBYTES (*lpbih) * lpbih->biHeight; // Is the format palatized or full-color if (lpbih->biBitCount <= 8 && lpbih->biClrUsed == 0) lpbih->biClrUsed = (1 << lpbih-> biBitCount); // paletized // See if the driver will support it if (dwError = SendDriverFormat (lpcs, lpbih, lpbih->biSize) ) return dwError; // Realloc our global header if (dwError = AllocNewGlobalBitmapInfo (lpcs, lpbih)) return dwError; // Realloc the bits if (dwError = AllocNewBitSpace (lpcs, lpbih)) return dwError; lpcs->dxBits = (int)lpbih->biWidth; lpcs->dyBits = (int)lpbih->biHeight; lpcs->VidHdr.lpData = lpcs->lpBits; lpcs->VidHdr.dwBufferLength = lpbih->biSizeImage; lpcs->VidHdr.dwUser = 0; lpcs->VidHdr.dwFlags = 0; return (DV_ERR_OK); } // // Returns: a LPBITMAPINFO allocated from global memory // containing the current format, or NULL on error. // Note that this structure can be larger than // sizeof (BITMAPINFO), ie. JPEG !!! // LPBITMAPINFO DibGetCurrentFormat (LPCAPSTREAM lpcs) { DWORD dwError; DWORD dwSize = 0; LPBITMAPINFO lpBInfo = NULL; if (!lpcs->fHardwareConnected) return NULL; // How large is the BITMAPINFOHEADER? videoConfigure( lpcs->hVideoIn, DVM_FORMAT, VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_QUERYSIZE, &dwSize, NULL, NULL, NULL, NULL); if (!dwSize) dwSize = sizeof (BITMAPINFOHEADER); if (!(lpBInfo = (LPBITMAPINFO) GlobalAllocPtr (GMEM_MOVEABLE, dwSize))) return (NULL); if (dwError = videoConfigure( lpcs->hVideoIn, DVM_FORMAT, VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_CURRENT, NULL, (LPBITMAPINFOHEADER) lpBInfo, dwSize, NULL, NULL ) ) { // very bad. the driver can't tell us its format. we're hosed. GlobalFreePtr (lpBInfo); return NULL; } return (lpBInfo); } // // Main entry point when changing capture formats. // This is called when the user closes the drivers format dialog. // Returns: 0 on success, or DV_ERR_... code // DWORD DibGetNewFormatFromDriver (LPCAPSTREAM lpcs) { BOOL f; BITMAPINFOHEADER bih; DWORD dwError; LPBITMAPINFO lpBInfo; if (!lpcs->fHardwareConnected) return DV_ERR_OK; // Return OK if no hardware exists lpBInfo = DibGetCurrentFormat (lpcs); if (lpBInfo == NULL) return DV_ERR_NOTSUPPORTED; // Set our internal state if (dwError = SetFormatFromDIB (lpcs, (LPBITMAPINFOHEADER) lpBInfo)) { // couldn't change formats, time to punt! // Try to switch back to minimal format (120x160x8) errorDriverID (lpcs, dwError); SetDefaultCaptureFormat (&bih); dwError = SetFormatFromDIB (lpcs, &bih); } // Force a new frame to be taken, so the DIB contains good // data. Especially important to prevent codecs from exploding! if (!dwError) videoFrame (lpcs->hVideoIn, &lpcs->VidHdr); if (lpBInfo) GlobalFreePtr (lpBInfo); f = DrawDibBegin(lpcs->hdd,NULL,-1,-1,(LPBITMAPINFOHEADER)(lpcs->lpBitsInfo),-1,-1,0); if (!f) errorUpdateError (lpcs, IDS_CAP_AVI_DRAWDIB_ERROR); return (dwError); } // // Main entry point when changing capture formats via App message. // Returns: TRUE on success, or FALSE if format not supported // BOOL DibNewFormatFromApp (LPCAPSTREAM lpcs, LPBITMAPINFO lpbiNew, WORD dwSize) { BOOL f; DWORD dwError; LPBITMAPINFO lpBInfo; if (!lpcs->fHardwareConnected) return FALSE; lpBInfo = DibGetCurrentFormat (lpcs); // Allocs memory!!! if (lpBInfo == NULL) return FALSE; // Set our internal state if (dwError = SetFormatFromDIB (lpcs, (LPBITMAPINFOHEADER) lpbiNew)) { // Driver didn't accept the format, // switch back to the original errorDriverID (lpcs, dwError); SetFormatFromDIB (lpcs, (LPBITMAPINFOHEADER)lpBInfo); } // Force a new frame to be taken, so the DIB contains good // data. Especially important to prevent codecs from exploding! videoFrame (lpcs->hVideoIn, &lpcs->VidHdr); if (lpBInfo) GlobalFreePtr (lpBInfo); f = DrawDibBegin(lpcs->hdd,NULL,-1,-1,(LPBITMAPINFOHEADER)(lpcs->lpBitsInfo),-1,-1,0); if (!f) errorDriverID (lpcs, IDS_CAP_AVI_DRAWDIB_ERROR); return (dwError == DV_ERR_OK); } void xlatClut8 (BYTE _huge *pb, DWORD dwSize, BYTE _huge *xlat) { DWORD dw; for (dw = 0; dw < dwSize; dw++, ((BYTE huge *)pb)++) *pb = xlat[*pb]; } // // DibNewPalette // // Performs three functions: // 1. Updates the biClrUsed field if biBitCount <= 8. // 2. Remaps BI_RGB images through a LUT when a new palette is assigned. // 3. Copies the palette entries into our global BITMAPINFO // // Returns: TRUE on success // DWORD DibNewPalette (LPCAPSTREAM lpcs, HPALETTE hPalNew) { LPBITMAPINFOHEADER lpbi; int n; int nColors; BYTE FAR * lpBits; RGBQUAD FAR * lpRgb; BYTE xlat[256]; DWORD dwSize; PALETTEENTRY pe; if (!hPalNew || !lpcs->lpBits || !lpcs->lpBitsInfo) return FALSE; lpbi = &(lpcs->lpBitsInfo->bmiHeader); lpRgb = (RGBQUAD FAR *)((LPSTR)lpbi + (WORD)lpbi->biSize); lpBits = lpcs->lpBits; GetObject(hPalNew, sizeof(int), (LPSTR) &nColors); if (nColors > 256) nColors = 256; // Get the palette entries regardless of the compression // Supermac uses non BI_RGB with a palette! if (lpbi->biBitCount == 8) { for (n=0; nbiBitCount == 8 && lpbi->biCompression == BI_RGB) { // // build a xlat table. from the old Palette to the new palette. // for (n=0; n<(int)lpbi->biClrUsed; n++) { xlat[n] = (BYTE)GetNearestPaletteIndex(hPalNew, RGB(lpRgb[n].rgbRed,lpRgb[n].rgbGreen,lpRgb[n].rgbBlue)); } // // translate the DIB bits // if ((dwSize = lpbi->biSizeImage) == 0) dwSize = lpbi->biHeight * DIBWIDTHBYTES(*lpbi); switch ((WORD)lpbi->biCompression) { case BI_RGB: xlatClut8(lpBits, dwSize, xlat); } } // Fix for Supermac, force biClrUsed to the number of palette entries // even if non-BI_RGB formats. if (lpbi-> biBitCount <= 8) lpbi->biClrUsed = nColors; return TRUE; } /* DibPaint(LPCAPSTREAM lpcs, hdc) * * Paint the current DIB into the window; */ void DibPaint(LPCAPSTREAM lpcs, HDC hdc) { RECT rc; BOOL fOK; fOK = (lpcs->lpBits != NULL); if (fOK) { if (lpcs-> fScale) { GetClientRect(lpcs->hwnd, &rc); fOK = DrawDibDraw(lpcs->hdd, hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, (LPBITMAPINFOHEADER)lpcs->lpBitsInfo, lpcs->lpBits, 0, 0, -1, -1, DDF_BACKGROUNDPAL); } else fOK = DrawDibDraw(lpcs->hdd, hdc, 0, 0, lpcs->dxBits, lpcs->dyBits, (LPBITMAPINFOHEADER)lpcs->lpBitsInfo, lpcs->lpBits, 0, 0, -1, -1, DDF_BACKGROUNDPAL); } if (!fOK) { SelectObject(hdc, GetStockObject(BLACK_BRUSH)); GetClientRect(lpcs->hwnd, &rc); PatBlt(hdc, 0, 0, rc.right, rc.bottom, PATCOPY); } } /* * * CreatePackedDib() - return the current DIB in packed (ie CF_DIB) format * */ HANDLE CreatePackedDib (LPBITMAPINFO lpBitsInfo, LPSTR lpSrcBits, HPALETTE hPalette) { HANDLE hdib; LPBITMAPINFO lpbi; int i; DWORD dwSize; PALETTEENTRY pe; LPBYTE lpBits; RGBQUAD FAR * lpRgb; // If the data is compressed, let ICM do the work for us... if ( lpBitsInfo->bmiHeader.biCompression != BI_RGB && lpBitsInfo->bmiHeader.biCompression != BI_RLE8 && (lpBitsInfo->bmiHeader.biBitCount != 8 || lpBitsInfo->bmiHeader.biBitCount != 24 )) { LPBITMAPINFO lpOutFormat = NULL; HANDLE hPackedDIBOut = NULL; if (!(lpOutFormat = (LPBITMAPINFO)GlobalAllocPtr( GMEM_MOVEABLE, sizeof (BITMAPINFOHEADER) + 256 * sizeof (RGBQUAD)))) return NULL; hmemcpy ((HPSTR)lpOutFormat, (HPSTR)lpBitsInfo, sizeof (BITMAPINFOHEADER)); // Try to get an RGB format lpOutFormat->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); lpOutFormat->bmiHeader.biCompression = BI_RGB; lpOutFormat->bmiHeader.biClrUsed = 0; lpOutFormat->bmiHeader.biClrImportant = 0; // Uh, oh, force to a 24-bit DIB if > 8 BPP if (lpBitsInfo->bmiHeader.biBitCount <= 8) lpOutFormat->bmiHeader.biBitCount = 8; else lpOutFormat->bmiHeader.biBitCount = 24; lpOutFormat->bmiHeader.biSizeImage = WIDTHBYTES (lpOutFormat->bmiHeader.biWidth * (lpOutFormat->bmiHeader.biBitCount == 8 ? 1 : 3)) * lpOutFormat->bmiHeader.biHeight; hPackedDIBOut = ICImageDecompress ( NULL, /*hic*/ 0, /*uiFlags*/ lpBitsInfo, /*lpbiIn*/ lpSrcBits, /*lpBits*/ lpOutFormat); /*use default format chosen by compressor*/ if (lpOutFormat) GlobalFreePtr (lpOutFormat); return (hPackedDIBOut); } dwSize = lpBitsInfo->bmiHeader.biSize + lpBitsInfo->bmiHeader.biClrUsed * sizeof(RGBQUAD) + lpBitsInfo->bmiHeader.biSizeImage; hdib = GlobalAlloc(GMEM_MOVEABLE, dwSize); if (!hdib) return NULL; lpbi = (LPVOID)GlobalLock(hdib); // // copy the header // hmemcpy ((HPSTR)lpbi, (HPSTR)lpBitsInfo, lpBitsInfo->bmiHeader.biSize); // // copy the color table // lpRgb = (RGBQUAD FAR *)((LPSTR)lpbi + (WORD)lpbi->bmiHeader.biSize); for (i=0; i < (int)lpBitsInfo->bmiHeader.biClrUsed; i++) { GetPaletteEntries(hPalette, i, 1, &pe); lpRgb[i].rgbRed = pe.peRed; lpRgb[i].rgbGreen = pe.peGreen; lpRgb[i].rgbBlue = pe.peBlue; lpRgb[i].rgbReserved = 0; } // // copy the bits. // lpBits = (LPBYTE)lpbi + lpbi->bmiHeader.biSize + lpbi->bmiHeader.biClrUsed * sizeof(RGBQUAD); hmemcpy ((LPSTR)lpBits, (LPSTR)lpSrcBits, lpbi->bmiHeader.biSizeImage); GlobalUnlock (hdib); return hdib; } /*---------------------------------------------------------------------+ | dibIsWritable() - return TRUE if the dib format is writable, | | by out dibWrite() function, FALSE if not. | | | +---------------------------------------------------------------------*/ BOOL FAR PASCAL dibIsWritable (LPBITMAPINFO lpBitsInfo) { if (!lpBitsInfo) return FALSE; // For now, just assume that all capture formats have an installed // codec which can convert to RGB. In the future, each time the // format is changed, test that the codec actually accepts the format. return TRUE; } /*---------------------------------------------------------------------+ | dibWrite() - write out the DIB to a file. The global header is | | in and the actual dib bits are in | | . If it is palettized then the palette is in | | . | | | | We won't do error reporting in this function, let the caller take | | care of that along with Opening and Closing the HMMIO. | | | +---------------------------------------------------------------------*/ BOOL FAR PASCAL dibWrite(LPCAPSTREAM lpcs, HMMIO hmmio) { BITMAPFILEHEADER bfh; DWORD dw; HANDLE hPackedDib = NULL; LPBITMAPINFO lpbi = NULL; BOOL fOK = FALSE; /* do some checking */ WinAssert(hmmio != 0); if (!lpcs->lpBits || !lpcs->lpBitsInfo) return FALSE; // Create a packed DIB, converting from a compressed format, // if necessary. hPackedDib = CreatePackedDib (lpcs->lpBitsInfo, lpcs->lpBits, lpcs->hPalCurrent); lpbi = (LPBITMAPINFO) GlobalLock (hPackedDib); if (!lpbi) goto WriteError; /* initialize the bitmap file header */ bfh.bfType = 'B' | 'M' << 8; bfh.bfSize = sizeof(bfh) + sizeof(BITMAPINFOHEADER) + lpbi->bmiHeader.biSizeImage + (lpbi->bmiHeader.biBitCount > 8 ? 0 : (lpbi->bmiHeader.biClrUsed * sizeof(RGBQUAD))); bfh.bfReserved1 = bfh.bfReserved2 = 0; bfh.bfOffBits = bfh.bfSize - lpbi->bmiHeader.biSizeImage ; // dw is the size of the BITMAPINFO + color table + image dw = bfh.bfSize - sizeof(bfh); /* write out the file header portion */ if (mmioWrite(hmmio, (HPSTR)&bfh, (LONG)sizeof(BITMAPFILEHEADER)) != sizeof(BITMAPFILEHEADER)){ goto WriteError; } /* now write out the header and bits */ if (mmioWrite(hmmio, (HPSTR)lpbi, (LONG) dw) == (LONG) dw) { fOK = TRUE; } WriteError: if (lpbi) GlobalUnlock (hPackedDib); if (hPackedDib) GlobalFree (hPackedDib); return fOK; } /*--------------------------------------------------------------+ | fileSaveDIB - save the frame as a DIB | | Top level routine to save a single frame | +--------------------------------------------------------------*/ BOOL FAR PASCAL fileSaveDIB(LPCAPSTREAM lpcs, LPSTR lpszFileName) { HMMIO hmmio; HCURSOR hOldCursor; BOOL fOK; hmmio = mmioOpen(lpszFileName, NULL, MMIO_WRITE); if( !hmmio ) { /* try and create */ hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE); if( !hmmio ) { /* find out if the file was read only or we are just */ /* totally hosed up here. */ hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ); if (hmmio){ /* file was read only, error on it */ errorUpdateError (lpcs, IDS_CAP_READONLYFILE, (LPSTR)lpszFileName); mmioClose(hmmio, 0); return FALSE; } else { /* even weirder error has occured here, give CANTOPEN */ errorUpdateError (lpcs, IDS_CAP_CANTOPEN, (LPSTR) lpszFileName); return FALSE; } } } hOldCursor = SetCursor( lpcs-> hWaitCursor ); mmioSeek(hmmio, 0, SEEK_SET); fOK = dibWrite(lpcs, hmmio); mmioClose( hmmio, 0 ); SetCursor( hOldCursor ); if (!fOK) errorUpdateError (lpcs, IDS_CAP_ERRORDIBSAVE, (LPSTR) lpszFileName); return fOK; }