// File: PalMap.cpp // Author: Michael Marr (mikemarr) // // History: // -@- 09/23/97 (mikemarr) copied to DXCConv from d2d\mmimage #include "stdafx.h" #include "PalMap.h" #include "Blt.h" #include "ddhelper.h" char gs_szPMPrefix[] = "palette map error"; CPaletteMap::CPaletteMap() { m_rgIndexMap = NULL; m_nConvertCode = cvcInvalid; m_cSrcBPP = m_cDstBPP = 0; m_bIdentity = FALSE; } CPaletteMap::~CPaletteMap() { MMDELETE(m_rgIndexMap); } // Function: CreateMap // This function creates a new mapping from a src palette to a destination color model. HRESULT CPaletteMap::CreateMap(BYTE nBPPSrcPixels, BYTE nBPPSrcPalette, LPPALETTEENTRY rgpeSrc, const CPixelInfo &pixiDst, LPDIRECTDRAWPALETTE pddpDst) { MMTRACE("CPaletteMap::CreateMap\n"); HRESULT hr; PALETTEENTRY rgpeDst[256]; DWORD dwDstCaps; // verify arguments if (rgpeSrc == NULL) return E_INVALIDARG; // delete the old index map, if it exists MMDELETE(m_rgIndexMap); // store the bit depths for mapping verification // REVIEW: perhaps the maps should be created with at least 256 entries always m_cSrcBPP = nBPPSrcPixels; m_cDstBPP = pixiDst.nBPP; // figure out what kind of conversion we are doing if ((m_nConvertCode = static_cast(GetConvertCode(m_cSrcBPP, m_cDstBPP))) == cvcInvalid) { MMTRACE("%s: can't convert from %d bit to %d bit\n", gs_szPMPrefix, (int) m_cSrcBPP, (int) m_cDstBPP); return E_INVALIDARG; } if (pddpDst == NULL) { // destination is RGB switch (m_cDstBPP) { case 16: return DoPalTo16BitMap(nBPPSrcPalette, pixiDst, rgpeSrc); break; case 24: return DoPalTo24BitMap(nBPPSrcPalette, pixiDst, rgpeSrc); break; case 32: return DoPalTo32BitMap(nBPPSrcPalette, pixiDst, rgpeSrc); break; default: return E_INVALIDARG; break; } } else { // destination is 8 bit palettized hr = E_INVALIDARG; if ((m_cDstBPP != 8) || // get the caps FAILED(pddpDst->GetCaps(&dwDstCaps)) || // verify we have True Color entries (dwDstCaps & DDPCAPS_8BITENTRIES) || // make sure the number of palette entries from the caps is 8 bits (!(dwDstCaps & DDPCAPS_8BIT)) || // get the palette entries FAILED(hr = pddpDst->GetEntries(0, 0, 1 << m_cDstBPP, rgpeDst))) { MMTRACE("%s: invalid dst palette for map\n", gs_szPMPrefix); return hr; } // create map for palette to palette return DoPalToPalMap(nBPPSrcPalette, m_cDstBPP, rgpeSrc, rgpeDst); } } HRESULT CPaletteMap::CreateMap(LPDIRECTDRAWPALETTE pddpSrc, const CPixelInfo &pixiDst, LPDIRECTDRAWPALETTE pddpDst) { // MMTRACE("CPaletteMap::CreateMap\n"); PALETTEENTRY rgpeSrc[256]; BYTE nBPPSrc; DWORD dwSrcCaps; // sanitize the src palette and get the srcBPP HRESULT hr = E_INVALIDARG; if ((pddpSrc == NULL) || // get the caps FAILED(pddpSrc->GetCaps(&dwSrcCaps)) || // verify we have True Color entries (dwSrcCaps & DDPCAPS_8BITENTRIES) || // get the number of palette entries from the caps ((nBPPSrc = BYTE(PaletteFlagsToBPP(dwSrcCaps))) == 0) || // get the palette entries FAILED(hr = pddpSrc->GetEntries(0, 0, (1 << nBPPSrc), rgpeSrc))) { MMTRACE("%s: invalid src palette for map\n", gs_szPMPrefix); return hr; } return CreateMap(nBPPSrc, nBPPSrc, rgpeSrc, pixiDst, pddpDst); } /* HRESULT CPaletteMap::CreateSortedMap(BYTE nBPP, const RGB *rgrgbSrc, BYTE nBPPUsed, DWORD iTransColor, DWORD dwFlags, LPPALETTEENTRY rgpeDst) { MMTRACE("CPaletteMap::CreateSortedMap\n"); MMASSERT(nBPP <= nBPPUsed); DWORD i, j, imin; if ((rgrgbSrc == NULL) || (nBPPUsed > 8)) return E_INVALIDARG; struct { DWORD nPos; int nLuminance; } rgSortMap[nMAXPALETTEENTRIES], minLuminance; // allocate the index map MMDELETE(m_rgIndexMap); m_rgIndexMap = (BYTE *) new BYTE[1 << nBPPUsed]; if (m_rgIndexMap == NULL) return E_OUTOFMEMORY; m_nConvertCode = GetConvertCode(nBPPUsed, nBPPUsed); MMASSERT(m_nConvertCode == cvc8To8); m_cSrcBPP = nBPPUsed; m_cDstBPP = nBPPUsed; // m_pixiDst.Init(nBPPUsed); // initialize the sort map (compute luminance values) DWORD cMapLength = (1 << nBPP), cTotalEntries = (1 << nBPPUsed); for (i = 0; i < cMapLength; i++) { const RGB &rgbTmp = rgrgbSrc[i]; rgSortMap[i].nPos = i; rgSortMap[i].nLuminance = nREDWEIGHT * rgbTmp.r + nGREENWEIGHT * rgbTmp.g + nBLUEWEIGHT * rgbTmp.b; } // if transparency exists, change its luminance to -1 so it will // become the zeroth index if (dwFlags & flagTRANSPARENT) { if (iTransColor > cMapLength) return E_INVALIDARG; rgSortMap[iTransColor].nLuminance = -1; } // sort the entries by luminance // REVIEW: use naive insertion sort for now for (i = 0; i < cMapLength; i++) { imin = i; minLuminance = rgSortMap[imin]; for (j = i + 1; j < cMapLength; j++) { if (minLuminance.nLuminance > rgSortMap[j].nLuminance) { imin = j; minLuminance = rgSortMap[imin]; } } rgSortMap[imin] = rgSortMap[i]; rgSortMap[i] = minLuminance; } // fill in the index map (sorting generates an "inverse" map) for (i = 0; i < cMapLength; i++) { m_rgIndexMap[rgSortMap[i].nPos] = (BYTE) i; } for (; i < cTotalEntries; i++) m_rgIndexMap[i] = (BYTE) i; // sort to a palette entry array based on this mapping if (rgpeDst) { for (i = 0; i < cMapLength; i++) { PALETTEENTRY &pe = rgpeDst[i]; const RGB &rgb = rgrgbSrc[rgSortMap[i].nPos]; pe.peRed = rgb.r; pe.peGreen = rgb.g; pe.peBlue = rgb.b; pe.peFlags = 0; } PALETTEENTRY peZero = {0, 0, 0, 0}; for (; i < cTotalEntries; i++) rgpeDst[i] = peZero; } return S_OK; } */ HRESULT CPaletteMap::DoPalTo16BitMap(BYTE cSrcBPP, const CPixelInfo &pixiDst, const PALETTEENTRY *ppeSrc) { MMASSERT(ppeSrc); DWORD cEntries = (1 << cSrcBPP); MapEntry16 *pIndexMap = new MapEntry16[cEntries]; if (pIndexMap == NULL) return E_OUTOFMEMORY; for (DWORD i = 0; i < cEntries; i++) { pIndexMap[i] = pixiDst.Pack16(ppeSrc[i]); } m_rgIndexMap = (BYTE *) pIndexMap; return S_OK; } HRESULT CPaletteMap::DoPalTo24BitMap(BYTE cSrcBPP, const CPixelInfo &pixiDst, const PALETTEENTRY *ppeSrc) { MMASSERT(ppeSrc); if ((pixiDst.nRedResidual | pixiDst.nGreenResidual | pixiDst.nBlueResidual) != 0) return DDERR_INVALIDPIXELFORMAT; DWORD cEntries = (1 << cSrcBPP); MapEntry24 *pIndexMap = new MapEntry24[cEntries]; if (pIndexMap == NULL) return E_OUTOFMEMORY; for (DWORD i = 0; i < cEntries; i++) { pIndexMap[i] = pixiDst.Pack(ppeSrc[i]); } m_rgIndexMap = (BYTE *) pIndexMap; return S_OK; } HRESULT CPaletteMap::DoPalTo32BitMap(BYTE cSrcBPP, const CPixelInfo &pixiDst, const PALETTEENTRY *ppeSrc) { // REVIEW: since PALETTEENTRY does not have an alpha field, // this should be the same as 24 bit return DoPalTo24BitMap(cSrcBPP, pixiDst, ppeSrc); } // blue is assumed to have a weight of 1.f #define fSimpleRedWeight 2.1f #define fSimpleGreenWeight 2.4f #define fMaxColorDistance ((1.f + fSimpleRedWeight + fSimpleGreenWeight) * float(257 * 256)) static inline float _ColorDistance(const PALETTEENTRY &pe, BYTE r, BYTE g, BYTE b) { float fTotal, fTmpR, fTmpG, fTmpB; fTmpR = (float) (pe.peRed - r); fTotal = fSimpleRedWeight * fTmpR * fTmpR; fTmpG = (float) (pe.peGreen - g); fTotal += fSimpleGreenWeight * fTmpG * fTmpG; fTmpB = (float) (pe.peBlue - b); // blue is assumed to have a weight of 1.f fTotal += fTmpB * fTmpB; return fTotal; } DWORD _SimpleFindClosestIndex(const PALETTEENTRY *rgpePalette, DWORD cEntries, BYTE r, BYTE g, BYTE b) { MMASSERT(rgpePalette); MMASSERT(cEntries <= nMAXPALETTEENTRIES); float fTmp, fMinDistance = fMaxColorDistance; DWORD nMinIndex = cEntries; for (DWORD i = 0; i < cEntries; i++) { const PALETTEENTRY &peTmp = rgpePalette[i]; if (!(peTmp.peFlags & (PC_RESERVED | PC_EXPLICIT))) { if ((fTmp = _ColorDistance(peTmp, r, g, b)) < fMinDistance) { // check for exact match if (fTmp == 0.f) return i; nMinIndex = i; fMinDistance = fTmp; } } } MMASSERT(nMinIndex < cEntries); return nMinIndex; } // Function: DoPalToPalMap // Compute a mapping from one palette to another and store in the palette map. HRESULT CPaletteMap::DoPalToPalMap(BYTE cSrcBPP, BYTE cDstBPP, const PALETTEENTRY *ppeSrc, const PALETTEENTRY *ppeDst) { MMASSERT(ppeSrc && ppeDst); DWORD cSrcEntries = (1 << cSrcBPP), cDstEntries = (1 << cDstBPP); m_rgIndexMap = new BYTE[cSrcEntries]; if (m_rgIndexMap == NULL) return E_OUTOFMEMORY; for (DWORD i = 0; i < cSrcEntries; i++) { const PALETTEENTRY &pe = ppeSrc[i]; m_rgIndexMap[i] = (BYTE) _SimpleFindClosestIndex(ppeDst, cDstEntries, pe.peRed, pe.peGreen, pe.peBlue); } return S_OK; } // Function: GetConvertCode // This function computes the index into the function arrays for // mapping and color conversion. int CPaletteMap::GetConvertCode(DWORD nSrcBPP, DWORD nDstBPP) { int nCode; if ((nDstBPP < 8) || (nSrcBPP > 8) || (nSrcBPP < 4)) { nCode = cvcInvalid; } else { nCode = (((nSrcBPP >> 2) - 1) << 2) | ((nDstBPP >> 3) - 1); } return nCode; } static DWORD GetColor8To8(DWORD dwSrcColor, const BYTE *pIndexMap) { MMASSERT(dwSrcColor < 256); return (DWORD) pIndexMap[dwSrcColor]; } static DWORD GetColor8To16(DWORD dwSrcColor, const BYTE *pIndexMap) { MMASSERT(dwSrcColor < 256); MapEntry16 *pIndexMap16 = (MapEntry16 *) pIndexMap; return (DWORD) pIndexMap16[dwSrcColor]; } static DWORD GetColor8To24(DWORD dwSrcColor, const BYTE *pIndexMap) { MMASSERT(dwSrcColor < 256); MapEntry24 *pIndexMap24 = (MapEntry24 *) pIndexMap; return (DWORD) pIndexMap24[dwSrcColor]; } static DWORD GetColor8To32(DWORD dwSrcColor, const BYTE *pIndexMap) { MMASSERT(dwSrcColor < 256); MapEntry32 *pIndexMap32 = (MapEntry32 *) pIndexMap; return (DWORD) pIndexMap32[dwSrcColor]; } static GetColorFunction gs_rgGetColorFunctions[cvcNumCodes] = { NULL, NULL, NULL, NULL, GetColor8To8, GetColor8To16, GetColor8To24, GetColor8To32 }; DWORD CPaletteMap::GetIndexMapping(DWORD iSrcColor) const { MMASSERT((m_nConvertCode < cvcInvalid) && (gs_rgGetColorFunctions[m_nConvertCode] != NULL)); return gs_rgGetColorFunctions[m_nConvertCode](iSrcColor, m_rgIndexMap); } // Notes: // The convert functions also fix the transparency on the destination objects. // A better way to do this stuff might be to have Blt functions and then separate // convert functions that cleanup the rest of the image after the Blt. ConvertFunction g_rgConvertFunctions[cvcNumCodes] = { NULL, NULL, NULL, NULL, BltFast8To8T, BltFast8To16T, BltFast8To24T, BltFast8To32T }; // Function: BltFast // This function takes a src dds and writes a dst dds using the // mapping defined by the PaletteMap. The src and dst can be the // same surface. HRESULT CPaletteMap::BltFast(LPDIRECTDRAWSURFACE pddsSrc, LPRECT prSrc, LPDIRECTDRAWSURFACE pddsDst, DWORD nXPos, DWORD nYPos, DWORD dwFlags) const { if (m_rgIndexMap == NULL) return E_NOTINITIALIZED; // make sure the surfaces are valid if (!pddsSrc || !pddsDst) { return E_INVALIDARG; } ConvertFunction pfnConvertFunction; HRESULT hr = E_INVALIDARG; BOOL bSrcLocked = FALSE, bDstLocked = FALSE; DDSURFACEDESC ddsdSrc, ddsdDst; INIT_DXSTRUCT(ddsdSrc); INIT_DXSTRUCT(ddsdDst); long nWidth, nHeight; // // Lock the surfaces // if (pddsSrc == pddsDst) { // REVIEW: this lock could just lock the minimum rectangle... if (FAILED(hr = pddsDst->Lock(NULL, &ddsdDst, DDLOCK_WAIT, NULL))) { goto e_Convert; } bSrcLocked = bDstLocked = TRUE; // copy the dst info into the src info ddsdSrc = ddsdDst; } else { // REVIEW: this lock could just lock the minimum rectangle... if (FAILED(hr = pddsSrc->Lock(NULL, &ddsdSrc, DDLOCK_WAIT, NULL))) goto e_Convert; bSrcLocked = TRUE; if (FAILED(hr = pddsDst->Lock(NULL, &ddsdDst, DDLOCK_WAIT, NULL))) goto e_Convert; bDstLocked = TRUE; } // verify the image information if ((ddsdSrc.ddpfPixelFormat.dwRGBBitCount != m_cSrcBPP) || (ddsdDst.ddpfPixelFormat.dwRGBBitCount != m_cDstBPP)) { hr = E_INVALIDARG; goto e_Convert; } // // clip // long nClipWidth, nClipHeight, nLeft, nTop; if (prSrc == NULL) { nWidth = ddsdSrc.dwWidth; nHeight = ddsdSrc.dwHeight; nLeft = 0; nTop = 0; } else { nWidth = prSrc->right - prSrc->left; nHeight = prSrc->bottom - prSrc->top; nLeft = prSrc->left; nTop = prSrc->top; } nClipWidth = long(ddsdDst.dwWidth - nXPos); nClipHeight = long(ddsdDst.dwHeight - nYPos); UPDATEMAX(nClipWidth, 0); UPDATEMAX(nClipHeight, 0); UPDATEMAX(nWidth, 0); UPDATEMAX(nHeight, 0); UPDATEMAX(nLeft, 0); UPDATEMAX(nTop, 0); UPDATEMIN(nClipWidth, nWidth); UPDATEMIN(nClipHeight, nHeight); if (((nLeft + nClipWidth) > long(ddsdSrc.dwWidth)) || ((nTop + nClipHeight) > long(ddsdSrc.dwHeight))) { hr = E_INVALIDARG; goto e_Convert; } // REVIEW: for now, fail if we are not dealing with at least 8BPP if ((ddsdSrc.ddpfPixelFormat.dwRGBBitCount < 8) || (ddsdDst.ddpfPixelFormat.dwRGBBitCount < 8)) { hr = E_FAIL; goto e_Convert; } nLeft *= (ddsdSrc.ddpfPixelFormat.dwRGBBitCount >> 3); nXPos *= (ddsdDst.ddpfPixelFormat.dwRGBBitCount >> 3); pfnConvertFunction = g_rgConvertFunctions[m_nConvertCode]; if (pfnConvertFunction) { hr = pfnConvertFunction( LPBYTE(ddsdSrc.lpSurface) + nLeft + (nTop * ddsdSrc.lPitch), ddsdSrc.lPitch, LPBYTE(ddsdDst.lpSurface) + nXPos + (nYPos * ddsdDst.lPitch), ddsdDst.lPitch, nClipWidth, nClipHeight, m_rgIndexMap); } else { hr = E_NOTIMPL; goto e_Convert; } e_Convert: // unlock the surfaces if (pddsSrc == pddsDst) { if (bSrcLocked) pddsDst->Unlock(ddsdDst.lpSurface); } else { if (bDstLocked) pddsDst->Unlock(ddsdDst.lpSurface); if (bSrcLocked) pddsSrc->Unlock(ddsdSrc.lpSurface); } MMASSERT(SUCCEEDED(hr)); return hr; }