1038 lines
33 KiB
C
1038 lines
33 KiB
C
|
/*******************************************************************************
|
||
|
* DXHelper.h *
|
||
|
*------------*
|
||
|
* Description:
|
||
|
* This is the header file for core helper functions implementation.
|
||
|
*-------------------------------------------------------------------------------
|
||
|
* Created By: Edward W. Connell Date: 07/11/95
|
||
|
* Copyright (C) 1995 Microsoft Corporation
|
||
|
* All Rights Reserved
|
||
|
*
|
||
|
*-------------------------------------------------------------------------------
|
||
|
* Revisions:
|
||
|
*
|
||
|
*******************************************************************************/
|
||
|
#ifndef DXHelper_h
|
||
|
#pragma option push -b -a8 -pc -A- /*P_O_Push*/
|
||
|
#define DXHelper_h
|
||
|
|
||
|
#ifndef DXTError_h
|
||
|
#include <DXTError.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef DXBounds_h
|
||
|
#include <DXBounds.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef __DXTrans_h__
|
||
|
#include <DXTrans.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef _INC_LIMITS
|
||
|
#include <limits.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef _INC_CRTDBG
|
||
|
#include <crtdbg.h>
|
||
|
#endif
|
||
|
|
||
|
#ifndef _INC_MALLOC
|
||
|
#include <malloc.h>
|
||
|
#endif
|
||
|
|
||
|
//=== Constants ==============================================================
|
||
|
|
||
|
#define DX_MMX_COUNT_CUTOFF 16
|
||
|
|
||
|
//=== Class, Enum, Struct and Union Declarations =============================
|
||
|
|
||
|
/*** DXLIMAPINFO
|
||
|
* This structure is used by the array linear interpolation and image
|
||
|
* filtering routines.
|
||
|
*/
|
||
|
typedef struct DXLIMAPINFO
|
||
|
{
|
||
|
float IndexFrac;
|
||
|
USHORT Index;
|
||
|
BYTE Weight;
|
||
|
} DXLIMAPINFO;
|
||
|
|
||
|
//
|
||
|
// Declare this class as a global to use for determining when to call MMX optimized
|
||
|
// code. You can use MinMMXOverCount to determine if MMX instructions are present.
|
||
|
// Typically, you would only want to use MMX instructions when you have a reasonably
|
||
|
// large number of pixels to work on. In this case your code can always be coded like
|
||
|
// this:
|
||
|
//
|
||
|
// if (CountOfPixelsToDo >= g_MMXInfo.MinMMXOverCount())
|
||
|
// {
|
||
|
// Do MMX Stuff
|
||
|
// } else {
|
||
|
// Do integer / float based stuff
|
||
|
// }
|
||
|
//
|
||
|
// If you code your MMX sequences like this, you will not have to use a special test
|
||
|
// for the presence of MMX since the MinMMXOverCount will be set to 0xFFFFFFFF if there
|
||
|
// is no MMX present on the processor.
|
||
|
//
|
||
|
// You do not need to use this unless your module needs to conditionally execute MMX vs
|
||
|
// non-MMX code. If you only call the helper functions provided by DXTrans.Dll, such as
|
||
|
// DXOverArrayMMX, you do NOT need this test. You can always call these functions and they
|
||
|
// will use the MMX code path only when MMX instructions are present.
|
||
|
//
|
||
|
class CDXMMXInfo
|
||
|
{
|
||
|
ULONG m_MinMMXOver;
|
||
|
public:
|
||
|
CDXMMXInfo()
|
||
|
{
|
||
|
#ifndef _X86_
|
||
|
m_MinMMXOver = 0xFFFFFFFF;
|
||
|
#else
|
||
|
m_MinMMXOver = DX_MMX_COUNT_CUTOFF;
|
||
|
__try
|
||
|
{
|
||
|
__asm
|
||
|
{
|
||
|
//--- Try the MMX exit multi-media state instruction
|
||
|
EMMS;
|
||
|
}
|
||
|
}
|
||
|
__except( GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION )
|
||
|
{
|
||
|
//--- MMX instructions not available
|
||
|
m_MinMMXOver = 0xFFFFFFFF;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
inline ULONG MinMMXOverCount() { return m_MinMMXOver; }
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
//=== Function Prototypes ==========================================
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXLinearInterpolateArray( const DXBASESAMPLE* pSamps, DXLIMAPINFO* pMapInfo,
|
||
|
DXBASESAMPLE* pResults, DWORD dwResultCount );
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXLinearInterpolateArray( const DXBASESAMPLE* pSamps, PUSHORT pIndexes,
|
||
|
PBYTE pWeights, DXBASESAMPLE* pResults,
|
||
|
DWORD dwResultCount );
|
||
|
|
||
|
//
|
||
|
// DXOverArray
|
||
|
//
|
||
|
// Composits an array of source samples over the samples in the pDest buffer.
|
||
|
//
|
||
|
// pDest - Pointer to the samples that will be modified by compositing the pSrc
|
||
|
// samples over the pDest samples.
|
||
|
// pSrc - The samples to composit over the pDest samples
|
||
|
// nCount - The number of samples to process
|
||
|
//
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXOverArray(DXPMSAMPLE* pDest, const DXPMSAMPLE* pSrc, ULONG nCount);
|
||
|
|
||
|
//
|
||
|
// DXOverArrayMMX
|
||
|
//
|
||
|
// Identical to DXOverArray except that the MMX instruction set will be used for
|
||
|
// large arrays of samples. If the CPU does not support MMX, you may still call
|
||
|
// this function, which will perform the same operation without the use of the MMX
|
||
|
// unit.
|
||
|
//
|
||
|
// Note that it is LESS EFFICIENT to use this function if the majority of the pixels
|
||
|
// in the pSrc buffer are either clear (alpha 0) or opaque (alpha 0xFF). This is
|
||
|
// because the MMX code must process every pixel and can not special case clear or
|
||
|
// opaque pixels. If there are a large number of translucent pixels then this function
|
||
|
// is much more efficent than DXOverArray.
|
||
|
//
|
||
|
// pDest - Pointer to the samples that will be modified by compositing the pSrc
|
||
|
// samples over the pDest samples.
|
||
|
// pSrc - The samples to composit over the pDest samples
|
||
|
// nCount - The number of samples to process
|
||
|
//
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXOverArrayMMX(DXPMSAMPLE* pDest, const DXPMSAMPLE* pSrc, ULONG nCount);
|
||
|
|
||
|
//
|
||
|
// DXConstOverArray
|
||
|
//
|
||
|
// Composits a single color over an array of samples.
|
||
|
//
|
||
|
// pDest - Pointer to the samples that will be modified by compositing the color (val)
|
||
|
// over the pDest samples.
|
||
|
// val - The premultiplied color value to composit over the pDest array.
|
||
|
// nCount - The number of samples to process
|
||
|
//
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXConstOverArray(DXPMSAMPLE* pDest, const DXPMSAMPLE & val, ULONG nCount);
|
||
|
|
||
|
//
|
||
|
// DXConstOverArray
|
||
|
//
|
||
|
// Composits a single color over an array of samples.
|
||
|
//
|
||
|
// pDest - Pointer to the samples that will be modified by compositing the samples
|
||
|
// in the buffer over the color (val).
|
||
|
// val - The premultiplied color value to composit under the pDest array.
|
||
|
// nCount - The number of samples to process
|
||
|
//
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXConstUnderArray(DXPMSAMPLE* pDest, const DXPMSAMPLE & val, ULONG nCount);
|
||
|
|
||
|
//===================================================================================
|
||
|
//
|
||
|
// Dithering Helpers
|
||
|
//
|
||
|
// Image transforms are sometimes asked to dither their output. This helper function
|
||
|
// should be used by all image transforms to enusure a consistant dither pattern.
|
||
|
//
|
||
|
// DXDitherArray is used to dither pixels prior to writing them to a DXSurface.
|
||
|
// The caller must fill in the DXDITHERDESC structure, setting X and Y to the
|
||
|
// output surface X,Y coordinates that the pixels will be placed in. The samples
|
||
|
// will be modified in place.
|
||
|
//
|
||
|
// Once the samples have been dithered, they should be written to or composited with
|
||
|
// the destination surface.
|
||
|
//
|
||
|
#define DX_DITHER_HEIGHT 4 // The dither pattern is 4x4 pixels
|
||
|
#define DX_DITHER_WIDTH 4
|
||
|
|
||
|
typedef struct DXDITHERDESC
|
||
|
{
|
||
|
DXBASESAMPLE * pSamples; // Pointer to the 32-bit samples to dither
|
||
|
ULONG cSamples; // Count of number of samples in pSamples buffer
|
||
|
ULONG x; // X coordinate of the output surface
|
||
|
ULONG y; // Y coordinate of the output surface
|
||
|
DXSAMPLEFORMATENUM DestSurfaceFmt; // Pixel format of the output surface
|
||
|
} DXDITHERDESC;
|
||
|
|
||
|
_DXTRANS_IMPL_EXT void WINAPI
|
||
|
DXDitherArray(const DXDITHERDESC *pDitherDesc);
|
||
|
|
||
|
//=== Enumerated Set Definitions =============================================
|
||
|
|
||
|
|
||
|
//=== Function Type Definitions ==============================================
|
||
|
|
||
|
|
||
|
//=== Class, Struct and Union Definitions ====================================
|
||
|
|
||
|
|
||
|
//=== Inline Functions =======================================================
|
||
|
|
||
|
//===================================================================================
|
||
|
//
|
||
|
// Memory allocation helpers.
|
||
|
//
|
||
|
// These macros are used to allocate arrays of samples from the stack (using _alloca)
|
||
|
// and cast them to the appropriate type. The ulNumSamples parameter is the count
|
||
|
// of samples required.
|
||
|
//
|
||
|
#define DXBASESAMPLE_Alloca( ulNumSamples ) \
|
||
|
(DXBASESAMPLE *)_alloca( (ulNumSamples) * sizeof( DXBASESAMPLE ) )
|
||
|
|
||
|
#define DXSAMPLE_Alloca( ulNumSamples ) \
|
||
|
(DXSAMPLE *)_alloca( (ulNumSamples) * sizeof( DXSAMPLE ) )
|
||
|
|
||
|
#define DXPMSAMPLE_Alloca( ulNumSamples ) \
|
||
|
(DXPMSAMPLE *)_alloca( (ulNumSamples) * sizeof( DXPMSAMPLE ) )
|
||
|
|
||
|
//===================================================================================
|
||
|
//
|
||
|
// Critical section helpers.
|
||
|
//
|
||
|
// These C++ classes, CDXAutoObjectLock and CDXAutoCritSecLock are used within functions
|
||
|
// to automatically claim critical sections upon constuction, and the critical section
|
||
|
// will be released when the object is destroyed (goes out of scope).
|
||
|
//
|
||
|
// The macros DXAUTO_OBJ_LOCK and DX_AUTO_SEC_LOCK(s) are normally used at the beginning
|
||
|
// of a function that requires a critical section. Any exit from the scope in which the
|
||
|
// auto-lock was taken will automatically release the lock.
|
||
|
//
|
||
|
|
||
|
#ifdef __ATLCOM_H__ //--- Only enable these if ATL is being used
|
||
|
class CDXAutoObjectLock
|
||
|
{
|
||
|
protected:
|
||
|
CComObjectRootEx<CComMultiThreadModel>* m_pObject;
|
||
|
|
||
|
public:
|
||
|
CDXAutoObjectLock(CComObjectRootEx<CComMultiThreadModel> * const pobject)
|
||
|
{
|
||
|
m_pObject = pobject;
|
||
|
m_pObject->Lock();
|
||
|
};
|
||
|
|
||
|
~CDXAutoObjectLock() {
|
||
|
m_pObject->Unlock();
|
||
|
};
|
||
|
};
|
||
|
|
||
|
#define DXAUTO_OBJ_LOCK CDXAutoObjectLock lck(this);
|
||
|
#define DXAUTO_OBJ_LOCK_( t ) CDXAutoObjectLock lck(t);
|
||
|
|
||
|
class CDXAutoCritSecLock
|
||
|
{
|
||
|
protected:
|
||
|
CComAutoCriticalSection* m_pSec;
|
||
|
|
||
|
public:
|
||
|
CDXAutoCritSecLock(CComAutoCriticalSection* pSec)
|
||
|
{
|
||
|
m_pSec = pSec;
|
||
|
m_pSec->Lock();
|
||
|
};
|
||
|
|
||
|
~CDXAutoCritSecLock()
|
||
|
{
|
||
|
m_pSec->Unlock();
|
||
|
};
|
||
|
};
|
||
|
|
||
|
#define DXAUTO_SEC_LOCK( s ) CDXAutoCritSecLock lck(s);
|
||
|
#endif // __ATLCOM_H__
|
||
|
|
||
|
//--- This function is used to compute the coefficient for a gaussian filter coordinate
|
||
|
inline float DXGaussCoeff( double x, double y, double Sigma )
|
||
|
{
|
||
|
double TwoSigmaSq = 2 * ( Sigma * Sigma );
|
||
|
return (float)(exp( ( -(x*x + y*y) / TwoSigmaSq ) ) /
|
||
|
( 3.1415927 * TwoSigmaSq ));
|
||
|
}
|
||
|
|
||
|
//--- This function is used to initialize a gaussian convolution filter
|
||
|
inline void DXInitGaussianFilter( float* pFilter, ULONG Width, ULONG Height, double Sigma )
|
||
|
{
|
||
|
int i, NumCoeff = Width * Height;
|
||
|
float val, CoeffAdjust, FilterSum = 0.;
|
||
|
double x, y;
|
||
|
double LeftX = -(double)(Width / 2);
|
||
|
double RightX = Width - LeftX;
|
||
|
double TopY = -(double)(Height / 2);
|
||
|
double BottomY = Height - TopY;
|
||
|
|
||
|
for( y = -TopY; y <= BottomY; y += 1. )
|
||
|
{
|
||
|
for( x = -LeftX; x <= RightX; x += 1. )
|
||
|
{
|
||
|
val = DXGaussCoeff( x, y, Sigma );
|
||
|
pFilter[i++] = val;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Normalize filter (make it sum to 1.0)
|
||
|
for( i = 0; i < NumCoeff; ++i ) FilterSum += pFilter[i];
|
||
|
|
||
|
if( FilterSum < 1. )
|
||
|
{
|
||
|
CoeffAdjust = 1.f / FilterSum;
|
||
|
for( i = 0; i < NumCoeff; ++i )
|
||
|
{
|
||
|
pFilter[i] *= CoeffAdjust;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} /* DXInitGaussianFilter*/
|
||
|
|
||
|
//
|
||
|
// DXConvertToGray
|
||
|
//
|
||
|
// Translates a color sample to a gray scale sample
|
||
|
//
|
||
|
// Sample - The sample to convert to gray scale.
|
||
|
// Return value is the gray scale sample.
|
||
|
//
|
||
|
inline DXBASESAMPLE DXConvertToGray( DXBASESAMPLE Sample )
|
||
|
{
|
||
|
DWORD v = Sample;
|
||
|
DWORD r = (BYTE)(v >> 16);
|
||
|
DWORD g = (BYTE)(v >> 8);
|
||
|
DWORD b = (BYTE)(v);
|
||
|
DWORD sat = (r*306 + g*601 + b*117) / 1024;
|
||
|
v &= 0xFF000000;
|
||
|
v |= (sat << 16) | (sat << 8) | sat;
|
||
|
return v;
|
||
|
} /* DXConvertToGray */
|
||
|
|
||
|
//--- This returns into the destination the value of the source
|
||
|
// sample scaled by its own alpha (producing a premultiplied alpha sample)
|
||
|
//
|
||
|
inline DXPMSAMPLE DXPreMultSample(const DXSAMPLE & Src)
|
||
|
{
|
||
|
if(Src.Alpha == 255 )
|
||
|
{
|
||
|
return (DWORD)Src;
|
||
|
}
|
||
|
else if(Src.Alpha == 0 )
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
unsigned t1, t2;
|
||
|
t1 = (Src & 0x00ff00ff) * Src.Alpha + 0x00800080;
|
||
|
t1 = ((t1 + ((t1 >> 8) & 0x00ff00ff)) >> 8) & 0x00ff00ff;
|
||
|
|
||
|
t2 = (((Src >> 8) & 0x000000ff) | 0x01000000) * Src.Alpha + 0x00800080;
|
||
|
t2 = (t2 + ((t2 >> 8) & 0x00ff00ff)) & 0xff00ff00;
|
||
|
return (t1 | t2);
|
||
|
}
|
||
|
} /* DXPreMultSample */
|
||
|
|
||
|
inline DXPMSAMPLE * DXPreMultArray(DXSAMPLE *pBuffer, ULONG cSamples)
|
||
|
{
|
||
|
for (ULONG i = 0; i < cSamples; i++)
|
||
|
{
|
||
|
BYTE SrcAlpha = pBuffer[i].Alpha;
|
||
|
if (SrcAlpha != 0xFF)
|
||
|
{
|
||
|
if (SrcAlpha == 0)
|
||
|
{
|
||
|
pBuffer[i] = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DWORD S = pBuffer[i];
|
||
|
DWORD t1 = (S & 0x00ff00ff) * SrcAlpha + 0x00800080;
|
||
|
t1 = ((t1 + ((t1 >> 8) & 0x00ff00ff)) >> 8) & 0x00ff00ff;
|
||
|
|
||
|
DWORD t2 = (((S >> 8) & 0x000000ff) | 0x01000000) * SrcAlpha + 0x00800080;
|
||
|
t2 = (t2 + ((t2 >> 8) & 0x00ff00ff)) & 0xff00ff00;
|
||
|
|
||
|
pBuffer[i] = (t1 | t2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return (DXPMSAMPLE *)pBuffer;
|
||
|
}
|
||
|
|
||
|
|
||
|
inline DXSAMPLE DXUnPreMultSample(const DXPMSAMPLE & Src)
|
||
|
{
|
||
|
if(Src.Alpha == 255 || Src.Alpha == 0)
|
||
|
{
|
||
|
return (DWORD)Src;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DXSAMPLE Dst;
|
||
|
Dst.Blue = (BYTE)((Src.Blue * 255) / Src.Alpha);
|
||
|
Dst.Green = (BYTE)((Src.Green * 255) / Src.Alpha);
|
||
|
Dst.Red = (BYTE)((Src.Red * 255) / Src.Alpha);
|
||
|
Dst.Alpha = Src.Alpha;
|
||
|
return Dst;
|
||
|
}
|
||
|
} /* DXUnPreMultSample */
|
||
|
|
||
|
inline DXSAMPLE * DXUnPreMultArray(DXPMSAMPLE *pBuffer, ULONG cSamples)
|
||
|
{
|
||
|
for (ULONG i = 0; i < cSamples; i++)
|
||
|
{
|
||
|
BYTE SrcAlpha = pBuffer[i].Alpha;
|
||
|
if (SrcAlpha != 0xFF && SrcAlpha != 0)
|
||
|
{
|
||
|
pBuffer[i].Blue = (BYTE)((pBuffer[i].Blue * 255) / SrcAlpha);
|
||
|
pBuffer[i].Green = (BYTE)((pBuffer[i].Green * 255) / SrcAlpha);
|
||
|
pBuffer[i].Red = (BYTE)((pBuffer[i].Red * 255) / SrcAlpha);
|
||
|
}
|
||
|
}
|
||
|
return (DXSAMPLE *)pBuffer;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// This returns the result of 255-Alpha which is computed by doing a NOT
|
||
|
//
|
||
|
inline BYTE DXInvertAlpha( BYTE Alpha ) { return (BYTE)~Alpha; }
|
||
|
|
||
|
inline DWORD DXScaleSample( DWORD Src, ULONG beta )
|
||
|
{
|
||
|
ULONG t1, t2;
|
||
|
|
||
|
t1 = (Src & 0x00ff00ff) * beta + 0x00800080;
|
||
|
t1 = ((t1 + ((t1 >> 8) & 0x00ff00ff)) >> 8) & 0x00ff00ff;
|
||
|
|
||
|
t2 = ((Src >> 8) & 0x00ff00ff) * beta + 0x00800080;
|
||
|
t2 = (t2 + ((t2 >> 8) & 0x00ff00ff)) & 0xff00ff00;
|
||
|
|
||
|
return (DWORD)(t1 | t2);
|
||
|
}
|
||
|
|
||
|
|
||
|
inline DWORD DXScaleSamplePercent( DWORD Src, float Percent )
|
||
|
{
|
||
|
if (Percent > (254.0f / 255.0f)) {
|
||
|
return Src;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return DXScaleSample(Src, (BYTE)(Percent * 255));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void DXCompositeOver(DXPMSAMPLE & Dst, const DXPMSAMPLE & Src)
|
||
|
{
|
||
|
if (Src.Alpha)
|
||
|
{
|
||
|
ULONG Beta = DXInvertAlpha(Src.Alpha);
|
||
|
if (Beta)
|
||
|
{
|
||
|
Dst = Src + DXScaleSample(Dst, Beta);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Dst = Src;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
inline DXPMSAMPLE DXCompositeUnder(DXPMSAMPLE Dst, DXPMSAMPLE Src )
|
||
|
{
|
||
|
return Dst + DXScaleSample(Src, DXInvertAlpha(Dst.Alpha));
|
||
|
}
|
||
|
|
||
|
|
||
|
inline DXBASESAMPLE DXApplyLookupTable(const DXBASESAMPLE Src, const BYTE * pTable)
|
||
|
{
|
||
|
DXBASESAMPLE Dest;
|
||
|
Dest.Blue = pTable[Src.Blue];
|
||
|
Dest.Green = pTable[Src.Green];
|
||
|
Dest.Red = pTable[Src.Red];
|
||
|
Dest.Alpha = pTable[Src.Alpha];
|
||
|
return Dest;
|
||
|
}
|
||
|
|
||
|
inline DXBASESAMPLE * DXApplyLookupTableArray(DXBASESAMPLE *pBuffer, ULONG cSamples, const BYTE * pTable)
|
||
|
{
|
||
|
for (ULONG i = 0; i < cSamples; i++)
|
||
|
{
|
||
|
DWORD v = pBuffer[i];
|
||
|
DWORD a = pTable[v >> 24];
|
||
|
DWORD r = pTable[(BYTE)(v >> 16)];
|
||
|
DWORD g = pTable[(BYTE)(v >> 8)];
|
||
|
DWORD b = pTable[(BYTE)v];
|
||
|
pBuffer[i] = (a << 24) | (r << 16) | (g << 8) | b;
|
||
|
}
|
||
|
return pBuffer;
|
||
|
}
|
||
|
|
||
|
inline DXBASESAMPLE * DXApplyColorChannelLookupArray(DXBASESAMPLE *pBuffer,
|
||
|
ULONG cSamples,
|
||
|
const BYTE * pAlphaTable,
|
||
|
const BYTE * pRedTable,
|
||
|
const BYTE * pGreenTable,
|
||
|
const BYTE * pBlueTable)
|
||
|
{
|
||
|
for (ULONG i = 0; i < cSamples; i++)
|
||
|
{
|
||
|
pBuffer[i].Blue = pBlueTable[pBuffer[i].Blue];
|
||
|
pBuffer[i].Green = pGreenTable[pBuffer[i].Green];
|
||
|
pBuffer[i].Red = pRedTable[pBuffer[i].Red];
|
||
|
pBuffer[i].Alpha = pAlphaTable[pBuffer[i].Alpha];
|
||
|
}
|
||
|
return pBuffer;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// CDXScale helper class
|
||
|
//
|
||
|
// This class uses a pre-computed lookup table to scale samples. For scaling large
|
||
|
// arrays of samples to a constant scale, this is much faster than using even MMX
|
||
|
// instructions. This class is usually declared as a member of another class and
|
||
|
// is most often used to apply a global opacity to a set of samples.
|
||
|
//
|
||
|
// When using this class, you must always check for the two special cases of clear
|
||
|
// and opaque before calling any of the scaling member functions. Do this by using
|
||
|
// the ScaleType() inline function. Your code should look somthing like this:
|
||
|
//
|
||
|
// if (ScaleType() == DXRUNTYPE_CLEAR)
|
||
|
// Do whatever you do for a 0 alpha set of samples -- usually just ignore them
|
||
|
// else if (ScaleType() == DXRUNTYPE_OPAQUE)
|
||
|
// Do whatever you would do for a non-scaled set of samples
|
||
|
// else
|
||
|
// Scale the samples by using ScaleSample or one of the ScaleArray members
|
||
|
//
|
||
|
// If you call any of the scaling members when the ScaleType() is either clear or
|
||
|
// opaque, you will GP fault becuase the lookup table will not be allocated.
|
||
|
//
|
||
|
// The scale can be set using either a floating point number between 0 and 1 using:
|
||
|
// CDXScale::SetScale / CDXScale::GetScale
|
||
|
// or you can use a byte integer value by using:
|
||
|
// CDXScale::SetScaleAlphaValue / CDXScale::GetScaleAlphaValue
|
||
|
//
|
||
|
class CDXScale
|
||
|
{
|
||
|
private:
|
||
|
float m_Scale;
|
||
|
BYTE m_AlphaScale;
|
||
|
BYTE *m_pTable;
|
||
|
|
||
|
HRESULT InternalSetScale(BYTE Scale)
|
||
|
{
|
||
|
if (m_AlphaScale == Scale) return S_OK;
|
||
|
if (Scale == 0 || Scale == 255)
|
||
|
{
|
||
|
delete m_pTable;
|
||
|
m_pTable = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!m_pTable)
|
||
|
{
|
||
|
m_pTable = new BYTE[256];
|
||
|
if(!m_pTable )
|
||
|
{
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
for (int i = 0; i < 256; ++i )
|
||
|
{
|
||
|
m_pTable[i] = (BYTE)((i * Scale) / 255);
|
||
|
}
|
||
|
}
|
||
|
m_AlphaScale = Scale;
|
||
|
return S_OK;
|
||
|
}
|
||
|
public:
|
||
|
CDXScale() :
|
||
|
m_Scale(1.0f),
|
||
|
m_AlphaScale(0xFF),
|
||
|
m_pTable(NULL)
|
||
|
{}
|
||
|
~CDXScale()
|
||
|
{
|
||
|
delete m_pTable;
|
||
|
}
|
||
|
DXRUNTYPE ScaleType()
|
||
|
{
|
||
|
if (m_AlphaScale == 0) return DXRUNTYPE_CLEAR;
|
||
|
if (m_AlphaScale == 0xFF) return DXRUNTYPE_OPAQUE;
|
||
|
return DXRUNTYPE_TRANS;
|
||
|
}
|
||
|
HRESULT SetScaleAlphaValue(BYTE Alpha)
|
||
|
{
|
||
|
HRESULT hr = InternalSetScale(Alpha);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
m_Scale = ((float)Alpha) / 255.0f;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
BYTE GetScaleAlphaValue(void)
|
||
|
{
|
||
|
return m_AlphaScale;
|
||
|
}
|
||
|
HRESULT SetScale(float Scale)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
if(( Scale < 0.0f ) || ( Scale > 1.0f ) )
|
||
|
{
|
||
|
hr = E_INVALIDARG;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ULONG IntScale = (ULONG)(Scale * 256.0f); // Round up alpha (.9999 = 255 = Solid)
|
||
|
if (IntScale > 255)
|
||
|
{
|
||
|
IntScale = 255;
|
||
|
}
|
||
|
hr = SetScaleAlphaValue((BYTE)IntScale);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
m_Scale = Scale;
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
float GetScale() const
|
||
|
{
|
||
|
return m_Scale;
|
||
|
}
|
||
|
DXRUNTYPE ScaleType() const
|
||
|
{
|
||
|
return (m_pTable ? DXRUNTYPE_TRANS : (m_AlphaScale ? DXRUNTYPE_OPAQUE : DXRUNTYPE_CLEAR));
|
||
|
}
|
||
|
DWORD ScaleSample(const DWORD s) const
|
||
|
{
|
||
|
return DXApplyLookupTable((DXBASESAMPLE)s, m_pTable);
|
||
|
}
|
||
|
DXBASESAMPLE * ScaleBaseArray(DXBASESAMPLE * pBuffer, ULONG cSamples) const
|
||
|
{
|
||
|
return DXApplyLookupTableArray(pBuffer, cSamples, m_pTable);
|
||
|
}
|
||
|
DXPMSAMPLE * ScalePremultArray(DXPMSAMPLE * pBuffer, ULONG cSamples) const
|
||
|
{
|
||
|
return (DXPMSAMPLE *)DXApplyLookupTableArray(pBuffer, cSamples, m_pTable);
|
||
|
}
|
||
|
DXSAMPLE * ScaleArray(DXSAMPLE * pBuffer, ULONG cSamples) const
|
||
|
{
|
||
|
return (DXSAMPLE *)DXApplyLookupTableArray(pBuffer, cSamples, m_pTable);
|
||
|
}
|
||
|
DXSAMPLE * ScaleArrayAlphaOnly(DXSAMPLE *pBuffer, ULONG cSamples) const
|
||
|
{
|
||
|
const BYTE *pTable = m_pTable;
|
||
|
for (ULONG i = 0; i < cSamples; i++)
|
||
|
{
|
||
|
pBuffer[i].Alpha = pTable[pBuffer[i].Alpha];
|
||
|
}
|
||
|
return pBuffer;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
inline DWORD DXWeightedAverage( DXBASESAMPLE S1, DXBASESAMPLE S2, ULONG Wgt )
|
||
|
{
|
||
|
_ASSERT( Wgt < 256 );
|
||
|
ULONG t1, t2;
|
||
|
ULONG InvWgt = Wgt ^ 0xFF;
|
||
|
|
||
|
t1 = (((S1 & 0x00ff00ff) * Wgt) + ((S2 & 0x00ff00ff) * InvWgt )) + 0x00800080;
|
||
|
t1 = ((t1 + ((t1 >> 8) & 0x00ff00ff)) >> 8) & 0x00ff00ff;
|
||
|
|
||
|
t2 = ((((S1 >> 8) & 0x00ff00ff) * Wgt) + (((S2 >> 8) & 0x00ff00ff) * InvWgt )) + 0x00800080;
|
||
|
t2 = (t2 + ((t2 >> 8) & 0x00ff00ff)) & 0xff00ff00;
|
||
|
|
||
|
return (t1 | t2);
|
||
|
} /* DXWeightedAverage */
|
||
|
|
||
|
inline void DXWeightedAverageArray( DXBASESAMPLE* pS1, DXBASESAMPLE* pS2, ULONG Wgt,
|
||
|
DXBASESAMPLE* pResults, DWORD dwCount )
|
||
|
{
|
||
|
_ASSERT( pS1 && pS2 && pResults && dwCount );
|
||
|
for( DWORD i = 0; i < dwCount; ++i )
|
||
|
{
|
||
|
pResults[i] = DXWeightedAverage( pS1[i], pS2[i], Wgt );
|
||
|
}
|
||
|
} /* DXWeightedAverageArray */
|
||
|
|
||
|
inline void DXWeightedAverageArrayOver( DXPMSAMPLE* pS1, DXPMSAMPLE* pS2, ULONG Wgt,
|
||
|
DXPMSAMPLE* pResults, DWORD dwCount )
|
||
|
{
|
||
|
_ASSERT( pS1 && pS2 && pResults && dwCount );
|
||
|
DWORD i;
|
||
|
|
||
|
if( Wgt == 255 )
|
||
|
{
|
||
|
for( i = 0; i < dwCount; ++i )
|
||
|
{
|
||
|
DXCompositeOver( pResults[i], pS1[i] );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for( i = 0; i < dwCount; ++i )
|
||
|
{
|
||
|
DXPMSAMPLE Avg = DXWeightedAverage( (DXBASESAMPLE)pS1[i],
|
||
|
(DXBASESAMPLE)pS2[i], Wgt );
|
||
|
DXCompositeOver( pResults[i], Avg );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} /* DXWeightedAverageArrayOver */
|
||
|
|
||
|
inline void DXScalePremultArray(DXPMSAMPLE *pBuffer, ULONG cSamples, BYTE Weight)
|
||
|
{
|
||
|
for (DXPMSAMPLE *pBuffLimit = pBuffer + cSamples; pBuffer < pBuffLimit; pBuffer++)
|
||
|
{
|
||
|
*pBuffer = DXScaleSample(*pBuffer, Weight);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
//
|
||
|
inline HRESULT DXClipToOutputWithPlacement(CDXDBnds & LogicalOutBnds, const CDXDBnds * pClipBnds, CDXDBnds & PhysicalOutBnds, const CDXDVec *pPlacement)
|
||
|
{
|
||
|
if(pClipBnds && (!LogicalOutBnds.IntersectBounds(*pClipBnds)))
|
||
|
{
|
||
|
return S_FALSE; // no intersect, we're done
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CDXDVec vClipPos(false);
|
||
|
LogicalOutBnds.GetMinVector( vClipPos );
|
||
|
if (pPlacement)
|
||
|
{
|
||
|
vClipPos -= *pPlacement;
|
||
|
}
|
||
|
PhysicalOutBnds += vClipPos;
|
||
|
if (!LogicalOutBnds.IntersectBounds(PhysicalOutBnds))
|
||
|
{
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
PhysicalOutBnds = LogicalOutBnds;
|
||
|
PhysicalOutBnds -= vClipPos;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Helper for converting a color ref to a DXSAMPLE
|
||
|
//
|
||
|
inline DWORD DXSampleFromColorRef(COLORREF cr)
|
||
|
{
|
||
|
DXSAMPLE Samp(0xFF, GetRValue(cr), GetGValue(cr), GetBValue(cr));
|
||
|
return Samp;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Fill an entire surface with a color
|
||
|
//
|
||
|
inline HRESULT DXFillSurface( IDXSurface *pSurface, DXPMSAMPLE Color,
|
||
|
BOOL bDoOver = FALSE, ULONG ulTimeOut = 10000 )
|
||
|
{
|
||
|
IDXARGBReadWritePtr * pPtr;
|
||
|
HRESULT hr = pSurface->LockSurface( NULL, ulTimeOut, DXLOCKF_READWRITE,
|
||
|
IID_IDXARGBReadWritePtr, (void **)&pPtr, NULL);
|
||
|
if( SUCCEEDED(hr) )
|
||
|
{
|
||
|
pPtr->FillRect(NULL, Color, bDoOver);
|
||
|
pPtr->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
} /* DXFillSurface */
|
||
|
|
||
|
//
|
||
|
// Fill a specified sub-rectangle of a surface with a color.
|
||
|
//
|
||
|
inline HRESULT DXFillSurfaceRect( IDXSurface *pSurface, RECT & rect, DXPMSAMPLE Color,
|
||
|
BOOL bDoOver = FALSE, ULONG ulTimeOut = 10000 )
|
||
|
{
|
||
|
CDXDBnds bnds(rect);
|
||
|
IDXARGBReadWritePtr * pPtr;
|
||
|
HRESULT hr = pSurface->LockSurface( &bnds, ulTimeOut, DXLOCKF_READWRITE,
|
||
|
IID_IDXARGBReadWritePtr, (void **)&pPtr, NULL);
|
||
|
if( SUCCEEDED(hr) )
|
||
|
{
|
||
|
pPtr->FillRect(NULL, Color, bDoOver);
|
||
|
pPtr->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
} /* DXFillSurfaceRect */
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// The DestBnds height and width must be greater than or equal to the source bounds.
|
||
|
//
|
||
|
// The dwFlags parameter uses the flags defined by IDXSurfaceFactory::BitBlt:
|
||
|
//
|
||
|
// DXBOF_DO_OVER
|
||
|
// DXBOF_DITHER
|
||
|
//
|
||
|
inline HRESULT DXBitBlt(IDXSurface * pDest, const CDXDBnds & DestBnds, IDXSurface * pSrc, const CDXDBnds & SrcBnds, DWORD dwFlags, ULONG ulTimeout)
|
||
|
{
|
||
|
IDXARGBReadPtr * pIn;
|
||
|
HRESULT hr;
|
||
|
hr = pSrc->LockSurface( &SrcBnds, INFINITE,
|
||
|
(dwFlags & DXBOF_DO_OVER) ? (DXLOCKF_READ | DXLOCKF_WANTRUNINFO) : DXLOCKF_READ,
|
||
|
IID_IDXARGBReadPtr, (void**)&pIn, NULL);
|
||
|
if(SUCCEEDED(hr))
|
||
|
{
|
||
|
IDXARGBReadWritePtr * pOut;
|
||
|
hr = pDest->LockSurface( &DestBnds, INFINITE, DXLOCKF_READWRITE,
|
||
|
IID_IDXARGBReadWritePtr, (void**)&pOut, NULL );
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
DXSAMPLEFORMATENUM InNativeType = pIn->GetNativeType(NULL);
|
||
|
DXSAMPLEFORMATENUM OutNativeType = pOut->GetNativeType(NULL);
|
||
|
BOOL bSrcIsOpaque = !(InNativeType & (DXPF_TRANSLUCENCY | DXPF_TRANSPARENCY));
|
||
|
const ULONG Width = SrcBnds.Width();
|
||
|
DXPMSAMPLE *pSrcBuff = NULL;
|
||
|
if( InNativeType != DXPF_PMARGB32 )
|
||
|
{
|
||
|
pSrcBuff = DXPMSAMPLE_Alloca(Width);
|
||
|
}
|
||
|
//
|
||
|
// Don't dither unless the dest has a greater error term than the source.
|
||
|
//
|
||
|
if ((dwFlags & DXBOF_DITHER) &&
|
||
|
((OutNativeType & DXPF_ERRORMASK) <= (InNativeType & DXPF_ERRORMASK)))
|
||
|
{
|
||
|
dwFlags &= (~DXBOF_DITHER);
|
||
|
}
|
||
|
if ((dwFlags & DXBOF_DITHER) || ((dwFlags & DXBOF_DO_OVER) && bSrcIsOpaque== 0))
|
||
|
{
|
||
|
//--- Allocate a working output buffer if necessary
|
||
|
DXPMSAMPLE *pDestBuff = NULL;
|
||
|
if( OutNativeType != DXPF_PMARGB32 )
|
||
|
{
|
||
|
pDestBuff = DXPMSAMPLE_Alloca(Width);
|
||
|
}
|
||
|
//--- Process each output row
|
||
|
// Note: Output coordinates are relative to the lock region
|
||
|
const ULONG Height = SrcBnds.Height();
|
||
|
if (dwFlags & DXBOF_DITHER)
|
||
|
{
|
||
|
DXPMSAMPLE * pSrcDitherBuff = pSrcBuff;
|
||
|
if (pSrcDitherBuff == NULL)
|
||
|
{
|
||
|
pSrcDitherBuff = DXPMSAMPLE_Alloca(Width);
|
||
|
}
|
||
|
const BOOL bCopy = ((dwFlags & DXBOF_DO_OVER) == 0);
|
||
|
//
|
||
|
// Set up the dither descriptor (some things are constant)
|
||
|
//
|
||
|
DXDITHERDESC dd;
|
||
|
dd.pSamples = pSrcDitherBuff;
|
||
|
dd.DestSurfaceFmt = OutNativeType;
|
||
|
for(ULONG Y = 0; Y < Height; ++Y )
|
||
|
{
|
||
|
dd.x = DestBnds.Left();
|
||
|
dd.y = DestBnds.Top() + Y;
|
||
|
const DXRUNINFO *pRunInfo;
|
||
|
ULONG cRuns = pIn->MoveAndGetRunInfo(Y, &pRunInfo);
|
||
|
pOut->MoveToRow( Y );
|
||
|
do
|
||
|
{
|
||
|
ULONG ulRunLen = pRunInfo->Count;
|
||
|
if (pRunInfo->Type == DXRUNTYPE_CLEAR)
|
||
|
{
|
||
|
pIn->Move(ulRunLen);
|
||
|
if (bCopy)
|
||
|
{
|
||
|
//
|
||
|
// The only way to avoid calling a constructor function to create
|
||
|
// a pmsample from 0 is to declare a variable and then assign it!
|
||
|
//
|
||
|
DXPMSAMPLE NullColor;
|
||
|
NullColor = 0;
|
||
|
pOut->FillAndMove(pSrcDitherBuff, NullColor, ulRunLen, FALSE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pOut->Move(ulRunLen);
|
||
|
}
|
||
|
dd.x += ulRunLen;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pIn->UnpackPremult(pSrcDitherBuff, ulRunLen, TRUE);
|
||
|
dd.cSamples = ulRunLen;
|
||
|
DXDitherArray(&dd);
|
||
|
dd.x += ulRunLen;
|
||
|
if (bCopy || pRunInfo->Type == DXRUNTYPE_OPAQUE)
|
||
|
{
|
||
|
pOut->PackPremultAndMove(pSrcDitherBuff, ulRunLen);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pOut->OverArrayAndMove(pDestBuff, pSrcDitherBuff, ulRunLen);
|
||
|
}
|
||
|
}
|
||
|
pRunInfo++;
|
||
|
cRuns--;
|
||
|
} while (cRuns);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for(ULONG Y = 0; Y < Height; ++Y )
|
||
|
{
|
||
|
const DXRUNINFO *pRunInfo;
|
||
|
ULONG cRuns = pIn->MoveAndGetRunInfo(Y, &pRunInfo);
|
||
|
pOut->MoveToRow( Y );
|
||
|
do
|
||
|
{
|
||
|
ULONG ulRunLen = pRunInfo->Count;
|
||
|
switch (pRunInfo->Type)
|
||
|
{
|
||
|
case DXRUNTYPE_CLEAR:
|
||
|
pIn->Move(ulRunLen);
|
||
|
pOut->Move(ulRunLen);
|
||
|
break;
|
||
|
case DXRUNTYPE_OPAQUE:
|
||
|
pOut->CopyAndMoveBoth(pDestBuff, pIn, ulRunLen, TRUE);
|
||
|
break;
|
||
|
case DXRUNTYPE_TRANS:
|
||
|
{
|
||
|
DXPMSAMPLE *pSrc = pIn->UnpackPremult(pSrcBuff, ulRunLen, TRUE);
|
||
|
DXPMSAMPLE *pDest = pOut->UnpackPremult(pDestBuff, ulRunLen, FALSE);
|
||
|
DXOverArrayMMX(pDest, pSrc, ulRunLen);
|
||
|
pOut->PackPremultAndMove(pDestBuff, ulRunLen);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DXRUNTYPE_UNKNOWN:
|
||
|
{
|
||
|
pOut->OverArrayAndMove(pDestBuff,
|
||
|
pIn->UnpackPremult(pSrcBuff, ulRunLen, TRUE),
|
||
|
ulRunLen);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
pRunInfo++;
|
||
|
cRuns--;
|
||
|
} while (cRuns);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pOut->CopyRect( pSrcBuff, NULL, pIn, NULL, bSrcIsOpaque );
|
||
|
}
|
||
|
pOut->Release();
|
||
|
}
|
||
|
pIn->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
inline HRESULT DXSrcCopy(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
|
||
|
IDXSurface *pSrcSurface, int nXSrc, int nYSrc)
|
||
|
{
|
||
|
IDXDCLock *pDCLock;
|
||
|
HRESULT hr = pSrcSurface->LockSurfaceDC(NULL, INFINITE, DXLOCKF_READ, &pDCLock);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
::BitBlt(hdcDest, nXDest, nYDest, nWidth, nHeight, pDCLock->GetDC(), nXSrc, nYSrc, SRCCOPY);
|
||
|
pDCLock->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
//
|
||
|
//=== Pointer validation functions
|
||
|
//
|
||
|
inline BOOL DXIsBadReadPtr( const void* pMem, UINT Size )
|
||
|
{
|
||
|
#if !defined( _DEBUG ) && defined( DXTRANS_NOROBUST )
|
||
|
return false;
|
||
|
#else
|
||
|
return ::IsBadReadPtr( pMem, Size );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
inline BOOL DXIsBadWritePtr( void* pMem, UINT Size )
|
||
|
{
|
||
|
#if !defined( _DEBUG ) && defined( DXTRANS_NOROBUST )
|
||
|
return false;
|
||
|
#else
|
||
|
return ::IsBadWritePtr( pMem, Size );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
inline BOOL DXIsBadInterfacePtr( const IUnknown* pUnknown )
|
||
|
{
|
||
|
#if !defined( _DEBUG ) && defined( DXTRANS_NOROBUST )
|
||
|
return false;
|
||
|
#else
|
||
|
return ( ::IsBadReadPtr( pUnknown, sizeof( *pUnknown ) ) ||
|
||
|
::IsBadCodePtr( (FARPROC)((PDWORD)pUnknown)[0] ))?
|
||
|
(true):(false);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#define DX_IS_BAD_OPTIONAL_WRITE_PTR(p) ((p) && DXIsBadWritePtr(p, sizeof(p)))
|
||
|
#define DX_IS_BAD_OPTIONAL_READ_PTR(p) ((p) && DXIsBadReadPtr(p, sizeof(p)))
|
||
|
#define DX_IS_BAD_OPTIONAL_INTERFACE_PTR(p) ((p) && DXIsBadInterfacePtr(p))
|
||
|
|
||
|
|
||
|
#pragma option pop /*P_O_Pop*/
|
||
|
#endif /* This must be the last line in the file */
|