You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9955 lines
277 KiB
9955 lines
277 KiB
/**************************************************************************\
|
|
*
|
|
* Copyright (c) 1999 Microsoft Corporation
|
|
*
|
|
* Abstract:
|
|
*
|
|
* Implemenation of GpBitmap class
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 06/28/1998 davidx
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "precomp.hpp"
|
|
|
|
#include "..\imaging\api\comutils.hpp"
|
|
#include "..\imaging\api\decodedimg.hpp"
|
|
#include "..\imaging\api\icmdll.hpp"
|
|
#include "..\imaging\api\memstream.hpp"
|
|
#include "..\imaging\api\imgutils.hpp"
|
|
#include "..\imaging\api\imgfactory.hpp"
|
|
#include "..\render\scanoperationinternal.hpp"
|
|
#include "..\render\FormatConverter.hpp"
|
|
#include "CopyOnWriteBitmap.hpp"
|
|
|
|
#define GDIP_TRANSPARENT_COLOR_KEY 0x000D0B0C
|
|
static const CLSID InternalJpegClsID =
|
|
{
|
|
0x557cf401,
|
|
0x1a04,
|
|
0x11d3,
|
|
{0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}
|
|
};
|
|
|
|
//!!! TO DO: need to go through all the routines to
|
|
// map image error codes to gdi+ error codes
|
|
|
|
GpStatus
|
|
MapHRESULTToGpStatus(HRESULT hr)
|
|
{
|
|
GpStatus status;
|
|
|
|
switch(hr)
|
|
{
|
|
case S_OK:
|
|
status = Ok;
|
|
break;
|
|
case E_INVALIDARG:
|
|
status = InvalidParameter;
|
|
break;
|
|
case E_OUTOFMEMORY:
|
|
status = OutOfMemory;
|
|
break;
|
|
case IMGERR_OBJECTBUSY:
|
|
status = ObjectBusy;
|
|
break;
|
|
case E_NOTIMPL:
|
|
status = NotImplemented;
|
|
break;
|
|
case IMGERR_ABORT:
|
|
status = Aborted;
|
|
break;
|
|
case IMGERR_CODECNOTFOUND:
|
|
case IMGERR_FAILLOADCODEC:
|
|
status = FileNotFound;
|
|
break;
|
|
case IMGERR_PROPERTYNOTFOUND:
|
|
status = PropertyNotFound;
|
|
break;
|
|
case IMGERR_PROPERTYNOTSUPPORTED:
|
|
status = PropertyNotSupported;
|
|
break;
|
|
default:
|
|
status = Win32Error;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
* ICM conversion from the embedded profile - if any - to SRGB
|
|
*
|
|
* Arguments:
|
|
* dstBitmap - pass in pointer to destination buffer or NULL to do conversion
|
|
* in place.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* The image is cloned and the operations performed on the clone.
|
|
* The result is returned in dst.
|
|
* NULL indicates that the operation didn't happen
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus CopyOnWriteBitmap::ICMFrontEnd(
|
|
CopyOnWriteBitmap **dstBitmap,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData,
|
|
GpRect *rect
|
|
)
|
|
{
|
|
|
|
// check to see if we're doing a conversion.
|
|
if(!ICMConvert || Globals::NoICM)
|
|
{
|
|
return Ok;
|
|
}
|
|
|
|
GpStatus status = Ok;
|
|
UINT size;
|
|
CopyOnWriteBitmap *dst = NULL;
|
|
|
|
status = GetPropertyItemSize(TAG_ICC_PROFILE, &size);
|
|
|
|
if(status==Ok)
|
|
{
|
|
PropertyItem *pi = (PropertyItem *)GpMalloc(size);
|
|
|
|
if(pi)
|
|
{
|
|
status = GetPropertyItem(TAG_ICC_PROFILE, size, pi);
|
|
}
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
|
|
if(status == Ok)
|
|
{
|
|
HRESULT hr = LoadICMDll();
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// Get Embedded profile
|
|
PROFILE p;
|
|
p.dwType = PROFILE_MEMBUFFER;
|
|
p.pProfileData = pi->value;
|
|
p.cbDataSize = size-sizeof(PropertyItem);
|
|
|
|
// destination profile for our internal space.
|
|
char profilename[40] = "sRGB Color Space Profile.icm";
|
|
PROFILE srgb;
|
|
srgb.dwType = PROFILE_FILENAME;
|
|
srgb.pProfileData = profilename;
|
|
srgb.cbDataSize = 40;
|
|
|
|
HPROFILE profiles[2];
|
|
profiles[0] = (*pfnOpenColorProfile)(&p,
|
|
PROFILE_READ,
|
|
FILE_SHARE_READ,
|
|
OPEN_EXISTING);
|
|
profiles[1] = (*pfnOpenColorProfile)(&srgb,
|
|
PROFILE_READ,
|
|
FILE_SHARE_READ,
|
|
OPEN_EXISTING);
|
|
|
|
if ( profiles[0] && profiles[1] )
|
|
{
|
|
HTRANSFORM trans;
|
|
DWORD intent[2] = {INTENT_PERCEPTUAL, INTENT_PERCEPTUAL};
|
|
trans = (*pfnCreateMultiProfileTransform)(
|
|
profiles, 2, intent, 2,
|
|
BEST_MODE | USE_RELATIVE_COLORIMETRIC,
|
|
0
|
|
);
|
|
if(trans)
|
|
{
|
|
// Translate bitmap bits at bit depth.
|
|
PixelFormatID pixFmt = PixelFormat32bppARGB;
|
|
if (IsIndexedPixelFormat(PixelFormatInMem))
|
|
{
|
|
pixFmt = PixelFormatInMem;
|
|
}
|
|
|
|
if(dstBitmap)
|
|
{
|
|
*dstBitmap = Clone(rect, pixFmt);
|
|
dst = *dstBitmap;
|
|
}
|
|
else
|
|
{
|
|
dst = this;
|
|
if (pixFmt != PixelFormatInMem)
|
|
{
|
|
ConvertFormat(pixFmt, callback, callbackData);
|
|
}
|
|
}
|
|
|
|
// Up to this point, PixelFormatInMem is preserved if
|
|
// indexed palette is available, otherwise we set to
|
|
// PIXFMT_32BPP_ARGB.
|
|
|
|
if(dst)
|
|
{
|
|
ASSERT(dst->Bmp != NULL);
|
|
ASSERT(dst->State == CopyOnWriteBitmap::MemBitmap);
|
|
|
|
BOOL result = FALSE;
|
|
|
|
if (IsIndexedPixelFormat(pixFmt))
|
|
{
|
|
// Translate the palette on bitmap
|
|
const ColorPalette *srcPalette;
|
|
|
|
srcPalette = dst->Bmp->GetCurrentPalette();
|
|
|
|
if (srcPalette != NULL)
|
|
{
|
|
ColorPalette *dstPalette;
|
|
dstPalette = CloneColorPalette(srcPalette, FALSE);
|
|
|
|
if (dstPalette != NULL)
|
|
{
|
|
// Do ICM
|
|
result = (*pfnTranslateBitmapBits)(
|
|
trans,
|
|
(PVOID)&(srcPalette->Entries[0]),
|
|
BM_xRGBQUADS,
|
|
1,
|
|
srcPalette->Count,
|
|
sizeof(ARGB),
|
|
(PVOID)&(dstPalette->Entries[0]),
|
|
BM_xRGBQUADS,
|
|
sizeof(ARGB),
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (result)
|
|
{
|
|
// Set transformed palette into
|
|
// the destination MemoryBitmap
|
|
|
|
hr = dst->Bmp->SetPalette(dstPalette);
|
|
if (FAILED(hr))
|
|
{
|
|
result = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DWORD err = GetLastError();
|
|
status = Win32Error;
|
|
WARNING(("TranslateBitmapBits failed %d", err));
|
|
}
|
|
|
|
|
|
GpFree(dstPalette);
|
|
}
|
|
}
|
|
}// Indexed format conversion
|
|
else
|
|
{
|
|
// Make a new Bmp structure.
|
|
GpMemoryBitmap *icmBmp = new GpMemoryBitmap();
|
|
|
|
if(icmBmp)
|
|
{
|
|
icmBmp->InitNewBitmap(
|
|
dst->Bmp->Width,
|
|
dst->Bmp->Height,
|
|
pixFmt
|
|
);
|
|
|
|
// Do ICM
|
|
|
|
BMFORMAT ulSrcColorMode = BM_xRGBQUADS;
|
|
BMFORMAT ulDstColorMode = BM_xRGBQUADS;
|
|
|
|
if ( dst->SrcImageInfo.Flags
|
|
& IMGFLAG_COLORSPACE_CMYK )
|
|
{
|
|
// Source image is in CMYK color space
|
|
|
|
ulSrcColorMode = BM_CMYKQUADS;
|
|
}
|
|
|
|
result = (*pfnTranslateBitmapBits)(
|
|
trans,
|
|
dst->Bmp->Scan0,
|
|
ulSrcColorMode,
|
|
dst->Bmp->Width,
|
|
dst->Bmp->Height,
|
|
dst->Bmp->Stride,
|
|
|
|
icmBmp->Scan0,
|
|
ulDstColorMode,
|
|
icmBmp->Stride,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (result)
|
|
{
|
|
// switch in the corrected bmp.
|
|
|
|
GpMemoryBitmap *tmp = dst->Bmp;
|
|
dst->Bmp = icmBmp;
|
|
icmBmp = tmp;
|
|
}
|
|
else
|
|
{
|
|
DWORD err = GetLastError();
|
|
status = Win32Error;
|
|
WARNING(("TranslateBitmapBits failed %d", err));
|
|
}
|
|
|
|
|
|
// delete the appropriate one - based on success or failure
|
|
// of the TranslateBitmapBits
|
|
|
|
delete icmBmp;
|
|
|
|
// Convert from RGB to 32 ARGB
|
|
// Note: ICC doesn't support alpha. If we fall into
|
|
// this piece of code, we are sure we are in
|
|
// 32BPP_RGB mode. So we can just set the image as
|
|
// opaque.
|
|
|
|
ASSERT(dst->PixelFormatInMem==PixelFormat32bppARGB);
|
|
|
|
BYTE* pBits = (BYTE*)dst->Bmp->Scan0;
|
|
|
|
for ( int i = 0; i < (int)dst->Bmp->Height; ++i )
|
|
{
|
|
BYTE* pTemp = pBits;
|
|
for ( int j = 0; j < (int)dst->Bmp->Width; ++j )
|
|
{
|
|
pTemp[3] = 0xff;
|
|
pTemp += 4;
|
|
}
|
|
|
|
pBits += dst->Bmp->Stride;
|
|
}
|
|
|
|
// If we have hacked the color format in
|
|
// LoadIntomemory() to avoid the color format
|
|
// conversion, then we need to restore it back
|
|
// here
|
|
|
|
if ( HasChangedRequiredPixelFormat == TRUE )
|
|
{
|
|
PixelFormatInMem = PixelFormat32bppPARGB;
|
|
HasChangedRequiredPixelFormat = FALSE;
|
|
}
|
|
}
|
|
else //if icmBmp
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = Win32Error;
|
|
WARNING(("Failed to clone bitmap\n"));
|
|
}
|
|
|
|
(*pfnDeleteColorTransform)(trans);
|
|
}// if ( trans )
|
|
else
|
|
{
|
|
status = Win32Error;
|
|
WARNING(("CreateMultiProfileTransform failed"));
|
|
}
|
|
}// if ( profiles[0] && profiles[1] )
|
|
else
|
|
{
|
|
status = Win32Error;
|
|
WARNING(("OpenColorProfile failed"));
|
|
}
|
|
|
|
if(profiles[0])
|
|
{
|
|
(*pfnCloseColorProfile)(profiles[0]);
|
|
}
|
|
|
|
if(profiles[1])
|
|
{
|
|
(*pfnCloseColorProfile)(profiles[1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = Win32Error;
|
|
WARNING(("Failed to load ICM dll\n"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("Failed to get the ICC property"));
|
|
}
|
|
GpFree(pi);
|
|
}
|
|
else
|
|
{
|
|
// Try do gamma and chromaticity.
|
|
PropertyItem *piGamma= NULL;
|
|
status = GetPropertyItemSize(TAG_GAMMA, &size);
|
|
if(status==Ok)
|
|
{
|
|
piGamma = (PropertyItem *)GpMalloc(size);
|
|
status = GetPropertyItem(TAG_GAMMA, size, piGamma);
|
|
}
|
|
else
|
|
{
|
|
status = Ok;
|
|
}
|
|
|
|
PropertyItem *piWhitePoint= NULL;
|
|
PropertyItem *piRGBPoint= NULL;
|
|
status = GetPropertyItemSize(TAG_WHITE_POINT, &size);
|
|
if(status==Ok)
|
|
{
|
|
piWhitePoint = (PropertyItem *)GpMalloc(size);
|
|
status = GetPropertyItem(TAG_WHITE_POINT, size, piWhitePoint);
|
|
}
|
|
|
|
status = GetPropertyItemSize(TAG_PRIMAY_CHROMATICS, &size);
|
|
if(status==Ok)
|
|
{
|
|
piRGBPoint = (PropertyItem *)GpMalloc(size);
|
|
status = GetPropertyItem(TAG_PRIMAY_CHROMATICS, size,
|
|
piRGBPoint);
|
|
}
|
|
else
|
|
{
|
|
status = Ok;
|
|
}
|
|
|
|
GpImageAttributes imageAttributes;
|
|
|
|
if(piGamma)
|
|
{
|
|
REAL gamma;
|
|
ASSERT((piGamma->type == TAG_TYPE_RATIONAL));
|
|
|
|
// get 1.0/gamma from the (source) gamma chunk
|
|
// formula is dstgamma/srcgamma
|
|
// we have to invert the gamma to account to how it is stored
|
|
// in the file format
|
|
gamma = (REAL)*((long *)piGamma->value)/ *((long *)piGamma->value+1);
|
|
gamma = gamma * 0.4545f; // our destination gamma is 1/2.2
|
|
|
|
// don't do any work if gamma is 1.0
|
|
// !!! need to work out what the best value for the tolerance is.
|
|
if(REALABS(gamma-1.0f) >= REAL_EPSILON)
|
|
{
|
|
imageAttributes.SetGamma(
|
|
ColorAdjustTypeBitmap, TRUE, gamma
|
|
);
|
|
}
|
|
|
|
}
|
|
using namespace VectorMath;
|
|
|
|
if(piWhitePoint && piRGBPoint)
|
|
{
|
|
Matrix R;
|
|
|
|
// Please refer to gdiplus\Specs\pngchrm.xls for all the formula
|
|
// and calculations below
|
|
|
|
LONG* llTemp = (long*)(piWhitePoint->value);
|
|
|
|
REAL Rx, Ry, Gx, Gy, Bx, By, Wx, Wy;
|
|
|
|
Wx = (REAL)llTemp[0] / llTemp[1];
|
|
Wy = (REAL)llTemp[2] / llTemp[3];
|
|
|
|
llTemp = (long*)(piRGBPoint->value);
|
|
|
|
Rx = (REAL)llTemp[0] / llTemp[1];
|
|
Ry = (REAL)llTemp[2] / llTemp[3];
|
|
Gx = (REAL)llTemp[4] / llTemp[5];
|
|
Gy = (REAL)llTemp[6] / llTemp[7];
|
|
Bx = (REAL)llTemp[8] / llTemp[9];
|
|
By = (REAL)llTemp[10] / llTemp[11];
|
|
|
|
// White point
|
|
Vector Wp(Wx, Wy, 1.0f-(Wx+Wy));
|
|
|
|
// Within some obscurely small amount
|
|
// !!! We need to work out what the actual tolerance should be.
|
|
BOOL accelerate =
|
|
(REALABS(Wx-0.3127f) < REAL_EPSILON) &&
|
|
(REALABS(Wy-0.3290f) < REAL_EPSILON);
|
|
|
|
Wp = Wp * (1.0f/Wy);
|
|
|
|
// Transpose of the input matrix.
|
|
Matrix I(
|
|
Rx, Gx, Bx,
|
|
Ry, Gy, By,
|
|
1.0f-(Rx+Ry), 1.0f-(Gx+Gy), 1.0f-(Bx+By)
|
|
);
|
|
|
|
Matrix II = I.Inverse();
|
|
|
|
Vector IIW = II*Wp;
|
|
Matrix DIIW(IIW); // Diagonalize vector IIW
|
|
Matrix Q = I*DIIW;
|
|
Matrix sRGB(
|
|
3.2406f, -1.5372f, -0.4986f,
|
|
-0.9689f, 1.8758f, 0.0415f,
|
|
0.0557f, -0.2040f, 1.0570f
|
|
);
|
|
|
|
if(accelerate)
|
|
{
|
|
R = sRGB*Q;
|
|
}
|
|
else
|
|
{
|
|
|
|
Matrix B(
|
|
0.40024f, 0.70760f, -0.08081f,
|
|
-0.22630f, 1.16532f, 0.04570f,
|
|
0.00000f, 0.00000f, 0.91822f
|
|
);
|
|
|
|
Matrix BI(
|
|
1.859936387f, -1.129381619f, 0.21989741f,
|
|
0.361191436f, 0.638812463f,-6.3706E-06f,
|
|
0.000000000f, 0.000000000f, 1.089063623f
|
|
);
|
|
|
|
Vector LMS = B * Wp;
|
|
|
|
// Get Diag( LMS^(-1) ), cell F50 in the XLS file
|
|
|
|
if ( LMS.data[0] != 0 )
|
|
{
|
|
LMS.data[0] = 1.0f / LMS.data[0];
|
|
}
|
|
|
|
if ( LMS.data[1] != 0 )
|
|
{
|
|
LMS.data[1] = 1.0f / LMS.data[1];
|
|
}
|
|
|
|
if ( LMS.data[2] != 0 )
|
|
{
|
|
LMS.data[2] = 1.0f / LMS.data[2];
|
|
}
|
|
|
|
Matrix L(LMS); // Diagonalize vector LMS
|
|
|
|
Matrix T = BI * L * B;
|
|
R = sRGB * T * Q;
|
|
}
|
|
|
|
// Make a 5x5 Color matrix for the recolor pipeline.
|
|
ColorMatrix ChM = {
|
|
R.data[0][0], R.data[1][0], R.data[2][0], 0, 0,
|
|
R.data[0][1], R.data[1][1], R.data[2][1], 0, 0,
|
|
R.data[0][2], R.data[1][2], R.data[2][2], 0, 0,
|
|
0, 0, 0, 1, 0,
|
|
0, 0, 0, 0, 1
|
|
};
|
|
|
|
imageAttributes.SetColorMatrix(
|
|
ColorAdjustTypeBitmap, TRUE, &ChM, NULL, ColorMatrixFlagsDefault
|
|
);
|
|
}
|
|
|
|
// If we initialized the imageAttributes to anything other than
|
|
// no-op then do the recoloring.
|
|
|
|
if(piGamma || (piWhitePoint && piRGBPoint))
|
|
{
|
|
// Note under certain conditions, the imageAttributes could still
|
|
// be no-op at this point. For instance if the gamma was really
|
|
// close to 1 and we had no chromaticities.
|
|
// Fortunately the recolor pipeline knows how to optimize the
|
|
// no-op case.
|
|
|
|
// Apply the Chromaticities and Gamma if they have been set.
|
|
status = Recolor(
|
|
imageAttributes.recolor,
|
|
dstBitmap, NULL, NULL, rect
|
|
);
|
|
|
|
// Recolor() will set Dirty flag on this image. Actually it is not
|
|
// dirty since we just apply the color correction on the image to
|
|
// display it. So we should reverse it back to not dirty.
|
|
// Note: this is a real issue for digital images from some cameras
|
|
// like Fuji. It always have White balance in it. If we don't do
|
|
// the reverse below, we can't do lossless transform on these
|
|
// images.
|
|
// Note: Unfortunately this kind of "dirty flag restore" breaks this
|
|
// scenario: (windows bug #583962)
|
|
// Source image is a 48 BPP PNG with embedded gamma. Without
|
|
// restoring the dirty flag here, if the caller asks for save(), we
|
|
// will save PNG using the bits in memory. If we set it to not dirty
|
|
// the save code path will let the PNG decoder talk to the encoder
|
|
// which will have 48 to 32 and to 48 conversion. This is a known
|
|
// GDI+ issue that this kind of conversion will produce wrong data.
|
|
// In order to avoid the PNG problem, we only restore the dirty flag
|
|
// here if the source is a JPEG image.
|
|
|
|
if (SrcImageInfo.RawDataFormat == IMGFMT_JPEG)
|
|
{
|
|
SetDirtyFlag(FALSE);
|
|
}
|
|
}
|
|
|
|
GpFree(piGamma);
|
|
GpFree(piWhitePoint);
|
|
GpFree(piRGBPoint);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
* Performs recoloring
|
|
*
|
|
* Arguments:
|
|
*
|
|
* recolor contains the recolor object.
|
|
* dstBitmap is the destination bitmap - set to NULL to recolor in place
|
|
*
|
|
* Return Value:
|
|
*
|
|
* The image is cloned and the operations performed on the clone.
|
|
* The result is returned in dst.
|
|
* NULL indicates that the operation didn't happen
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::Recolor(
|
|
GpRecolor *recolor,
|
|
CopyOnWriteBitmap **dstBitmap,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData,
|
|
GpRect *rect
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
CopyOnWriteBitmap *dst = NULL;
|
|
|
|
// If recolor exists, do color adjustment in a temporary bitmap.
|
|
|
|
if (recolor)
|
|
{
|
|
PixelFormatID pixfmt;
|
|
|
|
if (State >= MemBitmap)
|
|
{
|
|
// Bitmap has been decoded already
|
|
|
|
pixfmt = PixelFormatInMem;
|
|
}
|
|
else
|
|
{
|
|
// Bitmap hasn't been decoded yet. Let's make sure we
|
|
// decode it in a good format to avoid an expensive format
|
|
// conversion step later.
|
|
|
|
pixfmt = SrcImageInfo.PixelFormat;
|
|
}
|
|
|
|
// If indexed, color adjust natively; otherwise,
|
|
// convert to 32bpp ARGB and then do color adjust.
|
|
|
|
if (!IsIndexedPixelFormat(pixfmt))
|
|
{
|
|
pixfmt = PIXFMT_32BPP_ARGB;
|
|
}
|
|
|
|
if (dstBitmap)
|
|
{
|
|
*dstBitmap = Clone(rect, pixfmt);
|
|
dst = *dstBitmap;
|
|
}
|
|
else
|
|
{
|
|
dst = this;
|
|
ConvertFormat(pixfmt, callback, callbackData);
|
|
}
|
|
|
|
if (dst)
|
|
{
|
|
if (callback && ((*callback)(callbackData)))
|
|
{
|
|
status = Aborted;
|
|
}
|
|
|
|
if (status == Ok)
|
|
{
|
|
status = dst->ColorAdjust(recolor, pixfmt,
|
|
callback, callbackData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
return status;
|
|
}// Recolor()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* ICM corrects from an embedded profile - if any - to SRGB and then
|
|
* performs Recoloring.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* ImageAttributes contains the recolor object and the ICM on/off flag.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* The image is cloned and the operations performed on the clone.
|
|
* The result is returned in dst.
|
|
* NULL indicates that the operation didn't happen
|
|
*
|
|
\**************************************************************************/
|
|
|
|
/*GpStatus CopyOnWriteBitmap::RecolorAndICM(
|
|
GpImageAttributes *imageAttributes,
|
|
CopyOnWriteBitmap **dstBitmap,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData,
|
|
GpRect *rect
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
if(imageAttributes)
|
|
{
|
|
*dstBitmap = NULL;
|
|
|
|
if(imageAttributes->DeviceImageAttributes.ICMMode)
|
|
{
|
|
|
|
status = ICMFrontEnd(
|
|
dstBitmap, callback, callbackData, rect
|
|
);
|
|
|
|
if( (status == Ok) && (imageAttributes->recolor != NULL) )
|
|
{
|
|
status = (*dstBitmap)->ColorAdjust(
|
|
imageAttributes->recolor,
|
|
callback, callbackData
|
|
);
|
|
}
|
|
}
|
|
|
|
if(*dstBitmap == NULL)
|
|
{
|
|
status = Recolor(
|
|
imageAttributes->recolor,
|
|
dstBitmap, callback, callbackData, rect
|
|
);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Load an image from a file
|
|
*
|
|
* Arguments:
|
|
*
|
|
* filename - Specifies the name of the image file
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the newly loaded image object
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpImage*
|
|
GpImage::LoadImage(
|
|
const WCHAR* filename
|
|
)
|
|
{
|
|
// Try to create a metafile.
|
|
// If we do, and the metafile is valid then return it
|
|
// if the metafile isn't valid then create a bitmap
|
|
GpMetafile* metafile = new GpMetafile(filename);
|
|
if (metafile != NULL && !metafile->IsValid())
|
|
{
|
|
if (metafile->IsCorrupted())
|
|
{
|
|
metafile->Dispose();
|
|
return NULL;
|
|
}
|
|
|
|
// Dispose of the bad metafile and try a Bitmap
|
|
metafile->Dispose();
|
|
|
|
GpImage* bitmap = new GpBitmap(filename);
|
|
if (bitmap != NULL && !bitmap->IsValid())
|
|
{
|
|
bitmap->Dispose();
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return bitmap;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return metafile;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Load an image from an input data stream
|
|
*
|
|
* Arguments:
|
|
*
|
|
* stream - Specifies the input data stream
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the newly loaded image object
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpImage*
|
|
GpImage::LoadImage(
|
|
IStream* stream
|
|
)
|
|
{
|
|
// See if the stream is a metafile
|
|
GpMetafile* metafile = new GpMetafile(stream);
|
|
if (metafile != NULL)
|
|
{
|
|
if (metafile->IsValid())
|
|
return metafile;
|
|
else
|
|
{
|
|
BOOL isCorrupted = metafile->IsCorrupted();
|
|
metafile->Dispose();
|
|
if (isCorrupted)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// it's not a valid metafile -- it must be a bitmap
|
|
GpBitmap* bitmap = new GpBitmap(stream);
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Construct a bitmap image object from a file
|
|
*
|
|
* Arguments:
|
|
*
|
|
* filename - Specifies the name of the bitmap image file
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
const WCHAR* filename
|
|
)
|
|
{
|
|
InitDefaults();
|
|
Filename = UnicodeStringDuplicate(filename);
|
|
|
|
if ( Filename != NULL )
|
|
{
|
|
State = ImageRef;
|
|
}
|
|
|
|
if ( DereferenceStream() == Ok )
|
|
{
|
|
ASSERT(Img != NULL);
|
|
|
|
// Get source image info
|
|
|
|
if ( Img->GetImageInfo(&SrcImageInfo) == S_OK )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If we can't do a GetImageInfo(), there must be something wrong
|
|
// with this image. So we should release the DecodedImage object
|
|
// and set the State to Invalid
|
|
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(filename)---GetImageInfo() failed"));
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
|
|
GpFree(Filename);
|
|
Filename = NULL;
|
|
|
|
State = Invalid;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Construct a bitmap image object from a stream
|
|
*
|
|
* Arguments:
|
|
*
|
|
* stream - Specifies the input data stream
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
IStream* stream
|
|
)
|
|
{
|
|
InitDefaults();
|
|
|
|
Stream = stream;
|
|
Stream->AddRef();
|
|
State = ExtStream;
|
|
|
|
if ( DereferenceStream() == Ok )
|
|
{
|
|
ASSERT(Img != NULL);
|
|
|
|
// Get source image info
|
|
|
|
if ( Img->GetImageInfo(&SrcImageInfo) == S_OK )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If we can't do a GetImageInfo(), there must be something wrong
|
|
// with this image. So we should release the DecodedImage object
|
|
// and set the State to Invalid
|
|
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(stream)---GetImageInfo() failed"));
|
|
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
|
|
Stream->Release();
|
|
Stream = NULL;
|
|
State = Invalid;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Macro style function to save some common code in some constructors. The
|
|
* main purpose of this method is to cache the image info structure
|
|
*
|
|
* Arguments:
|
|
*
|
|
* hr - Specifies the return code from previous function call
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Note:
|
|
* Not an elegant method. Just for reducing code size
|
|
*
|
|
\**************************************************************************/
|
|
|
|
inline VOID
|
|
CopyOnWriteBitmap::CacheImageInfo(
|
|
HRESULT hr
|
|
)
|
|
{
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Fill image info structure
|
|
|
|
if ( Bmp->GetImageInfo(&SrcImageInfo) == S_OK )
|
|
{
|
|
State = MemBitmap;
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
return;
|
|
}
|
|
|
|
// There must be some problems if the basic GetImageInfo() failed.
|
|
// So we let it fall through to clean up even though the previous
|
|
// function succeed
|
|
// Notice: we haven't change the State yet. It is still at Invaliad
|
|
}
|
|
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap()----failed"));
|
|
delete Bmp;
|
|
Bmp = NULL;
|
|
|
|
return;
|
|
}// CacheImageInfo()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Derive a bitmap image from the given direct draw surface
|
|
*
|
|
* Arguments:
|
|
*
|
|
* surface - Direct draw surface
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
IDirectDrawSurface7 * surface
|
|
)
|
|
{
|
|
InitDefaults();
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if (!Bmp)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(IDirectDrawSurface7)----Out of memory"));
|
|
return;
|
|
}
|
|
|
|
HRESULT hr = Bmp->InitDirectDrawBitmap(surface);
|
|
|
|
CacheImageInfo(hr);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Create a bitmap image with the specified dimension and pixel format
|
|
*
|
|
* Arguments:
|
|
*
|
|
* width, height - Desired bitmap image dimension
|
|
* format - Desired pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
INT width,
|
|
INT height,
|
|
PixelFormatID format
|
|
)
|
|
{
|
|
InitDefaults();
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if (!Bmp)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::GpBimap(w, h, p)---Out of memory"));
|
|
return;
|
|
}
|
|
|
|
// Initialize the bitmap, clearing it (to opaque black).
|
|
|
|
HRESULT hr = Bmp->InitNewBitmap(width, height, format, TRUE);
|
|
|
|
CacheImageInfo(hr);
|
|
}
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
INT width,
|
|
INT height,
|
|
PixelFormatID format,
|
|
GpGraphics * graphics
|
|
)
|
|
{
|
|
InitDefaults();
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if (!Bmp)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(w, h, f, g)----Out of memory"));
|
|
return;
|
|
}
|
|
|
|
// Initialize the bitmap, clearing it (to opaque black).
|
|
|
|
HRESULT hr = Bmp->InitNewBitmap(width, height, format, TRUE);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
if ( Bmp->GetImageInfo(&SrcImageInfo) == S_OK )
|
|
{
|
|
REAL dpiX = graphics->GetDpiX();
|
|
REAL dpiY = graphics->GetDpiY();
|
|
|
|
// Note: SrcImageInfo will be updated for dpi in SetResolution()
|
|
|
|
if ( this->SetResolution(dpiX, dpiY) == Ok )
|
|
{
|
|
this->Display = graphics->IsDisplay();
|
|
}
|
|
|
|
State = MemBitmap;
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
return;
|
|
}
|
|
|
|
// There must be some problems if the basic GetImageInfo() failed. So we
|
|
// let it fall through to clean up even though InitNewBitmap() succeed
|
|
// Notice: we haven't change the State yet. It is still at Invaliad
|
|
}
|
|
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(w, h, f, g)---InitNewBitmap() failed"));
|
|
delete Bmp;
|
|
Bmp = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Create a bitmap image with the specified dimension and pixel format
|
|
*
|
|
* Arguments:
|
|
*
|
|
* width, height - Desired bitmap image dimension
|
|
* format - Desired pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
INT width,
|
|
INT height,
|
|
INT stride, // negative for bottom-up bitmaps
|
|
PixelFormatID format,
|
|
BYTE * scan0
|
|
)
|
|
{
|
|
this->InitDefaults();
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if (Bmp != NULL)
|
|
{
|
|
BitmapData bmData;
|
|
|
|
bmData.Width = width;
|
|
bmData.Height = height;
|
|
bmData.Stride = stride;
|
|
bmData.PixelFormat = format;
|
|
bmData.Scan0 = scan0;
|
|
bmData.Reserved = NULL;
|
|
|
|
HRESULT hr = Bmp->InitMemoryBitmap(&bmData);
|
|
|
|
CacheImageInfo(hr);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(w, h, s, f, scan)----Out of memory"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Create an in-memory bitmap image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* membmp - [IN]Memory bitmap current object is based on
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Note:
|
|
* This is a private constructor
|
|
*
|
|
* [minliu] It would be safer if this constructor did a Bmp->AddRef() and
|
|
* the caller was required to do a Bmp->Release() during its own cleanup.
|
|
* However, since we don't currently call Bmp->AddRef(), the caller MUST
|
|
* be very careful not to delete/release the GpMemoryBitmap passed into
|
|
* this contructor. Fortunately, this this constructor is only
|
|
* used in Clone() and GetThumbnail() and in each case the caller is
|
|
* properly managing the objects.
|
|
*
|
|
* New code which uses this contructor will similarly have to manage
|
|
* the membmp passed in properly.
|
|
*
|
|
\**************************************************************************/
|
|
inline
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
GpMemoryBitmap* membmp
|
|
)
|
|
{
|
|
ASSERT(membmp != NULL);
|
|
|
|
InitDefaults();
|
|
Bmp = membmp; // [minliu] Dangerous assignment, see header comments above
|
|
|
|
if ( Bmp->GetImageInfo(&SrcImageInfo) == S_OK )
|
|
{
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
State = MemBitmap;
|
|
}
|
|
else
|
|
{
|
|
Bmp = NULL;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Decode an RLE_8 bitmap into an 8bpp allocated bitmap. This only works
|
|
* for bitmaps with positive stride because the RLE stream doesn't necessarily
|
|
* respect end of line tags, and we would otherwise need to keep track of it.
|
|
* Caller must fix up in such cases.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* gdiBitmapInfo - Points to a BITMAPINFO, describing bitmap format
|
|
* gdiBitmapData - Points to the bits used to initialize image
|
|
* bitmapData----- Points to the BitmapData we return to the caller
|
|
*
|
|
* Comments:
|
|
*
|
|
* 10/13/2000 ericvan
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID *
|
|
DecodeCompressedRLEBitmap(
|
|
BITMAPINFO * gdiBitmapInfo,
|
|
VOID * gdiBitmapData,
|
|
BitmapData * bitmapData
|
|
)
|
|
{
|
|
ASSERT(gdiBitmapInfo->bmiHeader.biCompression == BI_RLE8);
|
|
ASSERT(gdiBitmapInfo->bmiHeader.biSizeImage>0);
|
|
ASSERT(gdiBitmapData != NULL);
|
|
|
|
BYTE* outputBitmap;
|
|
INT stride = bitmapData->Stride;
|
|
if (stride<0)
|
|
{
|
|
stride = -stride;
|
|
}
|
|
|
|
outputBitmap = (BYTE*)GpMalloc(stride*gdiBitmapInfo->bmiHeader.biHeight);
|
|
|
|
if (outputBitmap != NULL)
|
|
{
|
|
BYTE * srcPtr = (BYTE*)gdiBitmapData;
|
|
BYTE * endStrPtr = srcPtr + gdiBitmapInfo->bmiHeader.biSizeImage;
|
|
BYTE * dstPtr = outputBitmap;
|
|
BYTE * dstRasterPtr = outputBitmap;
|
|
BYTE * endDstPtr = outputBitmap + stride*gdiBitmapInfo->bmiHeader.biHeight;
|
|
|
|
while (srcPtr < endStrPtr)
|
|
{
|
|
INT numPixels = *srcPtr++;
|
|
|
|
if (numPixels == 0)
|
|
{
|
|
BYTE encode = *srcPtr++;
|
|
switch (encode)
|
|
{
|
|
case 0: // End of line.
|
|
dstRasterPtr += stride;
|
|
dstPtr = dstRasterPtr;
|
|
|
|
ASSERT(dstRasterPtr <= endDstPtr);
|
|
if (dstRasterPtr > endDstPtr)
|
|
{
|
|
GpFree(outputBitmap);
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case 1: // End of bitmap.
|
|
goto FinishedDecode;
|
|
|
|
case 2: // Delta. The 2 bytes following the escape contain
|
|
// unsigned values indicating the horizontal and vertical
|
|
// offsets of the next pixel from the current position.
|
|
{
|
|
BYTE horzOff = *srcPtr++;
|
|
BYTE vertOff = *srcPtr++;
|
|
|
|
dstPtr = dstPtr + horzOff + vertOff*stride;
|
|
dstRasterPtr += vertOff*stride;
|
|
|
|
ASSERT(dstRasterPtr <= endDstPtr);
|
|
if (dstRasterPtr > endDstPtr)
|
|
{
|
|
GpFree(outputBitmap);
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
numPixels = (INT)encode;
|
|
|
|
while (numPixels--)
|
|
{
|
|
*dstPtr++ = *srcPtr++;
|
|
}
|
|
|
|
// Force word alignment if not WORD aligned
|
|
if (((ULONG_PTR)srcPtr) % 2 == 1) srcPtr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BYTE outPixel = *srcPtr++;
|
|
|
|
while (numPixels--)
|
|
{
|
|
*dstPtr++ = outPixel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FinishedDecode:
|
|
|
|
if (outputBitmap && bitmapData->Stride<0)
|
|
{
|
|
BYTE* flippedBitmap = (BYTE *)GpMalloc(stride*gdiBitmapInfo->bmiHeader.biHeight);
|
|
|
|
if (flippedBitmap != NULL)
|
|
{
|
|
BYTE * srcPtr = outputBitmap + stride*(bitmapData->Height-1);
|
|
BYTE * dstPtr = flippedBitmap;
|
|
|
|
for (UINT cntY = 0; cntY<bitmapData->Height; cntY++)
|
|
{
|
|
GpMemcpy(dstPtr, srcPtr, stride);
|
|
srcPtr -= stride;
|
|
dstPtr += stride;
|
|
}
|
|
|
|
GpFree(outputBitmap);
|
|
outputBitmap = flippedBitmap;
|
|
bitmapData->Stride = stride;
|
|
}
|
|
}
|
|
|
|
return outputBitmap;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create a bitmap image from a GDI-style BITMAPINFO and pointer to
|
|
* the bits. Also, fill the ColorPalette info
|
|
*
|
|
* Arguments:
|
|
*
|
|
* gdiBitmapInfo - Points to a BITMAPINFO, describing bitmap format
|
|
* gdiBitmapData - Points to the bits used to initialize image
|
|
* bitmapData----- Points to the BitmapData we return to the caller
|
|
* palette-------- Points to a ColorPalette which will be filled in this method
|
|
*
|
|
* Comments:
|
|
*
|
|
* Does not handle compressed formats. Not identified as a customer need,
|
|
* but could add for completeness...
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL
|
|
ValidateBitmapInfo(
|
|
BITMAPINFO* gdiBitmapInfo,
|
|
VOID* gdiBitmapData,
|
|
BitmapData* bitmapData,
|
|
ColorPalette* palette
|
|
)
|
|
{
|
|
BOOL status = FALSE;
|
|
|
|
ASSERT(gdiBitmapInfo != NULL);
|
|
ASSERT(gdiBitmapData != NULL);
|
|
ASSERT(bitmapData != NULL);
|
|
ASSERT(palette != NULL);
|
|
|
|
// Only understand BI_RGB and BI_BITFIELDS.
|
|
|
|
//!!!TODO: could handle BI_JPEG and BI_PNG by creating a GpMemoryStream
|
|
//!!! and passing to Bitmap::Bitmap(IStream*)
|
|
|
|
//!!!TODO: could handle BI_RLEx by creating a DIB to render into and
|
|
//!!! grabbing the decompressed bits
|
|
|
|
if ((gdiBitmapInfo->bmiHeader.biCompression != BI_RGB) &&
|
|
(gdiBitmapInfo->bmiHeader.biCompression != BI_BITFIELDS) &&
|
|
(gdiBitmapInfo->bmiHeader.biCompression != BI_RLE8))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
// Scanlines are aligned to 4 byte boundaries.
|
|
|
|
INT colorBits = gdiBitmapInfo->bmiHeader.biPlanes *
|
|
gdiBitmapInfo->bmiHeader.biBitCount;
|
|
|
|
INT stride = (((gdiBitmapInfo->bmiHeader.biWidth * colorBits) + 31)
|
|
& ~31) / 8;
|
|
|
|
// Determine GDI+ Pixelformat. Note that GDI bitmaps do not have alpha.
|
|
|
|
PixelFormatID format = PIXFMT_UNDEFINED;
|
|
|
|
switch (colorBits)
|
|
{
|
|
case 1:
|
|
|
|
format = PIXFMT_1BPP_INDEXED;
|
|
break;
|
|
|
|
case 4:
|
|
|
|
format = PIXFMT_4BPP_INDEXED;
|
|
break;
|
|
|
|
case 8:
|
|
|
|
format = PIXFMT_8BPP_INDEXED;
|
|
break;
|
|
|
|
case 16:
|
|
|
|
if (gdiBitmapInfo->bmiHeader.biCompression == BI_RGB)
|
|
format = PIXFMT_16BPP_RGB555;
|
|
else
|
|
{
|
|
ASSERT(gdiBitmapInfo->bmiHeader.biCompression == BI_BITFIELDS);
|
|
|
|
ULONG* colorMasks = reinterpret_cast<ULONG*>
|
|
(&gdiBitmapInfo->bmiColors[0]);
|
|
|
|
if ((colorMasks[0] == 0x00007c00) && // red
|
|
(colorMasks[1] == 0x000003e0) && // green
|
|
(colorMasks[2] == 0x0000001f)) // blue
|
|
format = PIXFMT_16BPP_RGB555;
|
|
else if ((colorMasks[0] == 0x0000F800) && // red
|
|
(colorMasks[1] == 0x000007e0) && // green
|
|
(colorMasks[2] == 0x0000001f)) // blue
|
|
format = PIXFMT_16BPP_RGB565;
|
|
|
|
//!!!TODO: Win9x does not support any other combination for
|
|
//!!! 16bpp BI_BITFIELDS. WinNT does and we could support
|
|
//!!! via same mechanism as for BI_RLEx, but is it worth it?
|
|
}
|
|
break;
|
|
|
|
case 24:
|
|
|
|
format = PIXFMT_24BPP_RGB;
|
|
break;
|
|
|
|
case 32:
|
|
|
|
if (gdiBitmapInfo->bmiHeader.biCompression == BI_RGB)
|
|
format = PIXFMT_32BPP_RGB;
|
|
else
|
|
{
|
|
ASSERT(gdiBitmapInfo->bmiHeader.biCompression == BI_BITFIELDS);
|
|
|
|
ULONG* colorMasks = reinterpret_cast<ULONG*>
|
|
(&gdiBitmapInfo->bmiColors[0]);
|
|
|
|
if ((colorMasks[0] == 0x00ff0000) && // red
|
|
(colorMasks[1] == 0x0000ff00) && // green
|
|
(colorMasks[2] == 0x000000ff)) // blue
|
|
format = PIXFMT_32BPP_RGB;
|
|
else
|
|
format = PIXFMT_UNDEFINED;
|
|
|
|
//!!!TODO: Win9x does not support any other combination for
|
|
//!!! 32bpp BI_BITFIELDS. WinNT does and we could support
|
|
//!!! via same mechanism as for BI_RLEx, but is it worth it?
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
format = PIXFMT_UNDEFINED;
|
|
break;
|
|
}
|
|
|
|
if (format == PIXFMT_UNDEFINED)
|
|
return status;
|
|
|
|
// Deal with color table.
|
|
|
|
switch(format)
|
|
{
|
|
case PIXFMT_1BPP_INDEXED:
|
|
case PIXFMT_4BPP_INDEXED:
|
|
case PIXFMT_8BPP_INDEXED:
|
|
|
|
palette->Count = 1 << colorBits;
|
|
|
|
if ((gdiBitmapInfo->bmiHeader.biClrUsed > 0) &&
|
|
(gdiBitmapInfo->bmiHeader.biClrUsed < palette->Count))
|
|
palette->Count = gdiBitmapInfo->bmiHeader.biClrUsed;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
palette->Count = 0;
|
|
break;
|
|
}
|
|
|
|
ASSERT(palette->Count <= 256);
|
|
if (palette->Count)
|
|
{
|
|
palette->Flags = 0;
|
|
|
|
RGBQUAD* rgb = gdiBitmapInfo->bmiColors;
|
|
ARGB* argb = palette->Entries;
|
|
ARGB* argbEnd = argb + palette->Count;
|
|
|
|
for (; argb < argbEnd; argb++, rgb++)
|
|
{
|
|
*argb = Color::MakeARGB(255, rgb->rgbRed, rgb->rgbGreen, rgb->rgbBlue);
|
|
}
|
|
}
|
|
|
|
// Compute scan0. The stride will allow us to determine top-down or
|
|
// bottom-up.
|
|
|
|
VOID* scan0;
|
|
INT height;
|
|
|
|
if (gdiBitmapInfo->bmiHeader.biHeight > 0)
|
|
{
|
|
// Bottom-up:
|
|
|
|
height = gdiBitmapInfo->bmiHeader.biHeight;
|
|
scan0 = static_cast<VOID*>
|
|
(static_cast<BYTE*>(gdiBitmapData) + (height - 1) * stride);
|
|
stride = -stride;
|
|
}
|
|
else
|
|
{
|
|
// Top-down:
|
|
|
|
height = -gdiBitmapInfo->bmiHeader.biHeight;
|
|
scan0 = gdiBitmapData;
|
|
}
|
|
|
|
// Setup the BitmapData.
|
|
|
|
bitmapData->Width = gdiBitmapInfo->bmiHeader.biWidth;
|
|
bitmapData->Height = height;
|
|
bitmapData->Stride = stride;
|
|
bitmapData->PixelFormat = format;
|
|
bitmapData->Scan0 = scan0;
|
|
bitmapData->Reserved = NULL;
|
|
|
|
status = TRUE;
|
|
|
|
return status;
|
|
}
|
|
|
|
CopyOnWriteBitmap::CopyOnWriteBitmap(
|
|
BITMAPINFO* gdiBitmapInfo,
|
|
VOID* gdiBitmapData,
|
|
BOOL ownBitmapData
|
|
)
|
|
{
|
|
this->InitDefaults();
|
|
|
|
if ( ownBitmapData )
|
|
{
|
|
cleanupBitmapData = gdiBitmapData;
|
|
}
|
|
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if ( Bmp != NULL )
|
|
{
|
|
BitmapData bitmapData;
|
|
UINT colorTableSize;
|
|
BYTE paletteBuffer[sizeof(ColorPalette) + 255*sizeof(ARGB)];
|
|
ColorPalette* palette = reinterpret_cast<ColorPalette*>
|
|
(&paletteBuffer[0]);
|
|
|
|
// Validate image info
|
|
// Note: "palette" and "bitmapData" structures will be filled after
|
|
// return from ValidateBitmapInfo()
|
|
|
|
if ( ValidateBitmapInfo(gdiBitmapInfo, gdiBitmapData,
|
|
&bitmapData, palette) )
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (gdiBitmapInfo->bmiHeader.biCompression == BI_RLE8)
|
|
{
|
|
VOID* decodedBitmapBits;
|
|
|
|
decodedBitmapBits = DecodeCompressedRLEBitmap(gdiBitmapInfo,
|
|
gdiBitmapData,
|
|
&bitmapData);
|
|
if (decodedBitmapBits == NULL)
|
|
{
|
|
goto CleanupBmp;
|
|
}
|
|
|
|
if (ownBitmapData)
|
|
{
|
|
GpFree(gdiBitmapData);
|
|
}
|
|
|
|
cleanupBitmapData = decodedBitmapBits;
|
|
ownBitmapData = TRUE;
|
|
bitmapData.Scan0 = cleanupBitmapData;
|
|
}
|
|
|
|
hr = Bmp->InitMemoryBitmap(&bitmapData);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Set the current state
|
|
|
|
State = MemBitmap;
|
|
|
|
// If it is indexed mode, set the palette
|
|
|
|
if ( palette->Count )
|
|
{
|
|
hr = Bmp->SetPalette(palette);
|
|
}
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Set proper image flags
|
|
|
|
UINT imageFlags;
|
|
BITMAPINFOHEADER *bmih = &gdiBitmapInfo->bmiHeader;
|
|
imageFlags = SinkFlagsTopDown
|
|
| SinkFlagsFullWidth
|
|
| ImageFlagsHasRealPixelSize
|
|
| ImageFlagsColorSpaceRGB;
|
|
|
|
// If both XPelsPerMeter and YPelsPerMeter are greater than
|
|
// 0, then we claim that the file has real dpi info in the
|
|
// flags. Otherwise, claim that the dpi's are fake.
|
|
|
|
if ( (bmih->biXPelsPerMeter > 0)
|
|
&&(bmih->biYPelsPerMeter > 0) )
|
|
{
|
|
imageFlags |= ImageFlagsHasRealDPI;
|
|
}
|
|
|
|
hr = Bmp->SetImageFlags(imageFlags);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Get source image info
|
|
|
|
hr = Bmp->GetImageInfo(&SrcImageInfo);
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
// Return successfully
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("::CopyOnWriteBitmap(b, d)-GetImageInfo() failed"));
|
|
}
|
|
}
|
|
}
|
|
}// If ( SUCCEEDED() on InitMemoryBitmap() )
|
|
|
|
CleanupBmp:
|
|
;
|
|
}// if ( ValidateBitmapInfo() )
|
|
|
|
// If we fall into here, it means something is wrong above if the basic
|
|
// GetImageInfo() or SetImageFlags() failed.
|
|
// So we let it fall through to clean up
|
|
// Notice: we have to reset the State to Invaliad afetr clean up
|
|
|
|
WARNING(("CopyOnWriteBitmap::CopyOnWriteBitmap(bmpinfo, data)--InitMemoryBitmap failed"));
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
State = Invalid;
|
|
|
|
return;
|
|
}// If ( Bmp != NULL )
|
|
|
|
WARNING(("Out of memory"));
|
|
return;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create CopyOnWriteBitmap from a GDI HBITMAP. The HBITMAP must not be selected
|
|
* into an HDC. The hpal defines the color table if hbm is a 4bpp or 8bpp
|
|
* DDB.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* hbm -- Initialize CopyOnWriteBitmap with contents of this HBITMAP
|
|
* hpal -- Defines color table if hbm is palettized DDB
|
|
* bitmap -- Return created bitmap via this buffer
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successful
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::CreateFromHBITMAP(
|
|
HBITMAP hbm,
|
|
HPALETTE hpal,
|
|
CopyOnWriteBitmap** bitmap
|
|
)
|
|
{
|
|
GpStatus status = Win32Error;
|
|
|
|
BYTE bufferBitmapInfo[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
|
|
BITMAPINFO *gdiBitmapInfo = (BITMAPINFO *) bufferBitmapInfo;
|
|
|
|
memset(bufferBitmapInfo, 0, sizeof(bufferBitmapInfo));
|
|
|
|
HDC hdc = CreateCompatibleDC(NULL);
|
|
if (hdc)
|
|
{
|
|
// Select palette (ignored if bitmap is not DDB or not palettized):
|
|
|
|
HPALETTE hpalOld = (HPALETTE) SelectObject(hdc, hpal);
|
|
|
|
// Call GetDIBits to get info about size, etc. of the GDI bitmap:
|
|
|
|
gdiBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
|
|
if (GetDIBits(hdc, hbm, 0, 0, NULL, gdiBitmapInfo, DIB_RGB_COLORS) &&
|
|
(gdiBitmapInfo->bmiHeader.biSizeImage != 0))
|
|
{
|
|
// Allocate memory for the bitmap bits:
|
|
|
|
VOID *gdiBitmapData = GpMalloc(gdiBitmapInfo->bmiHeader.biSizeImage);
|
|
|
|
if (gdiBitmapData != NULL)
|
|
{
|
|
// Get the bitmap bits:
|
|
|
|
if (GetDIBits(hdc, hbm,
|
|
0, abs(gdiBitmapInfo->bmiHeader.biHeight),
|
|
gdiBitmapData, gdiBitmapInfo, DIB_RGB_COLORS))
|
|
{
|
|
// Create a GDI+ bitmap from the BITMAPINFO and bits.
|
|
// Let the GDI+ bitmap take ownership of the memory
|
|
// (i.e., Bitmap::Dispose() will delete the bitmap
|
|
// bits buffer):
|
|
|
|
*bitmap = new CopyOnWriteBitmap(gdiBitmapInfo, gdiBitmapData, TRUE);
|
|
|
|
if (*bitmap != NULL)
|
|
{
|
|
if ((*bitmap)->IsValid())
|
|
status = Ok;
|
|
else
|
|
{
|
|
(*bitmap)->Dispose();
|
|
*bitmap = NULL;
|
|
status = InvalidParameter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Bitmap ctor failed, so we still have responsiblity
|
|
// for cleaning up the bitmap bits buffer:
|
|
|
|
GpFree(gdiBitmapData);
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GpFree(gdiBitmapData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
|
|
SelectObject(hdc, hpalOld);
|
|
DeleteDC(hdc);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create CopyOnWriteBitmap from a Win32 HICON.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* hicon -- Initialize CopyOnWriteBitmap with contents of this HICON
|
|
* bitmap -- Return created bitmap via this buffer
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successful
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID ImportMask32BPP(BitmapData* dst, BitmapData* mask)
|
|
{
|
|
ASSERT(dst->PixelFormat == PIXFMT_32BPP_ARGB);
|
|
ASSERT(mask->PixelFormat == PIXFMT_32BPP_RGB);
|
|
ASSERT(dst->Width == mask->Width);
|
|
ASSERT(dst->Height == mask->Height);
|
|
ASSERT(dst->Scan0 != NULL);
|
|
ASSERT(mask->Scan0 != NULL);
|
|
|
|
BYTE* dstScan = static_cast<BYTE*>(dst->Scan0);
|
|
BYTE* maskScan = static_cast<BYTE*>(mask->Scan0);
|
|
|
|
for (UINT row = 0; row < dst->Height; row++)
|
|
{
|
|
ARGB *dstPixel = static_cast<ARGB*>(static_cast<VOID*>(dstScan));
|
|
ARGB *maskPixel = static_cast<ARGB*>(static_cast<VOID*>(maskScan));
|
|
|
|
for (UINT col = 0; col < dst->Width; col++)
|
|
{
|
|
if (*maskPixel)
|
|
*dstPixel = 0;
|
|
|
|
dstPixel++;
|
|
maskPixel++;
|
|
}
|
|
|
|
dstScan = dstScan + dst->Stride;
|
|
maskScan = maskScan + mask->Stride;
|
|
}
|
|
}
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::CreateFromHICON(
|
|
HICON hicon,
|
|
CopyOnWriteBitmap** bitmap
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
// Get icon bitmaps via Win32:
|
|
|
|
ICONINFO iconInfo;
|
|
|
|
if (GetIconInfo(hicon, &iconInfo))
|
|
{
|
|
if (iconInfo.fIcon && (iconInfo.hbmColor != NULL))
|
|
{
|
|
// Create a Bitmap from the icon's hbmColor:
|
|
|
|
status = CreateFromHBITMAP(iconInfo.hbmColor,
|
|
(HPALETTE)GetStockObject(DEFAULT_PALETTE),
|
|
bitmap);
|
|
|
|
// Convert Bitmap to 32bpp ARGB (need the alpha channel):
|
|
|
|
if (status == Ok && (*bitmap != NULL))
|
|
(*bitmap)->ConvertFormat(PIXFMT_32BPP_ARGB, NULL, NULL);
|
|
|
|
// Retrieve the icon mask:
|
|
|
|
if ((status == Ok) && (iconInfo.hbmMask != NULL))
|
|
{
|
|
status = Win32Error;
|
|
|
|
HDC hdc = GetDC(NULL);
|
|
|
|
if (hdc)
|
|
{
|
|
// Get some basic information about the bitmap mask:
|
|
|
|
BYTE bufferBitmapInfo[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
|
|
BITMAPINFO *gdiBitmapInfo = (BITMAPINFO *) bufferBitmapInfo;
|
|
|
|
memset(bufferBitmapInfo, 0, sizeof(bufferBitmapInfo));
|
|
gdiBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
|
|
if (GetDIBits(hdc,
|
|
iconInfo.hbmMask,
|
|
0,
|
|
0,
|
|
NULL,
|
|
gdiBitmapInfo,
|
|
DIB_RGB_COLORS))
|
|
{
|
|
// Get the bitmap mask as a 32bpp top-down DIB:
|
|
|
|
gdiBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
gdiBitmapInfo->bmiHeader.biHeight = -abs(gdiBitmapInfo->bmiHeader.biHeight);
|
|
gdiBitmapInfo->bmiHeader.biPlanes = 1;
|
|
gdiBitmapInfo->bmiHeader.biBitCount = 32;
|
|
gdiBitmapInfo->bmiHeader.biCompression = BI_RGB;
|
|
gdiBitmapInfo->bmiHeader.biSizeImage = 0;
|
|
gdiBitmapInfo->bmiHeader.biClrUsed = 0;
|
|
gdiBitmapInfo->bmiHeader.biClrImportant = 0;
|
|
|
|
VOID *gdiBitmapData = GpMalloc(gdiBitmapInfo->bmiHeader.biHeight
|
|
* gdiBitmapInfo->bmiHeader.biHeight
|
|
* 4);
|
|
|
|
if (gdiBitmapData != NULL)
|
|
{
|
|
if (GetDIBits(hdc,
|
|
iconInfo.hbmMask,
|
|
0,
|
|
-gdiBitmapInfo->bmiHeader.biHeight,
|
|
gdiBitmapData,
|
|
gdiBitmapInfo,
|
|
DIB_RGB_COLORS))
|
|
{
|
|
// Convert non-zero mask values to alpha = 0:
|
|
|
|
BitmapData bmpData;
|
|
|
|
status = (*bitmap)->LockBits(NULL,
|
|
IMGLOCK_READ|IMGLOCK_WRITE,
|
|
PIXFMT_32BPP_ARGB,
|
|
&bmpData);
|
|
|
|
if (status == Ok)
|
|
{
|
|
BitmapData maskData;
|
|
|
|
maskData.Width = gdiBitmapInfo->bmiHeader.biWidth;
|
|
maskData.Height = -gdiBitmapInfo->bmiHeader.biHeight;
|
|
maskData.Stride = gdiBitmapInfo->bmiHeader.biWidth * 4;
|
|
maskData.PixelFormat = PIXFMT_32BPP_RGB;
|
|
maskData.Scan0 = gdiBitmapData;
|
|
maskData.Reserved = 0;
|
|
|
|
ImportMask32BPP(&bmpData, &maskData);
|
|
|
|
(*bitmap)->UnlockBits(&bmpData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("GetDIBits failed on icon mask bitmap"));
|
|
}
|
|
|
|
GpFree(gdiBitmapData);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("memory allocation failed"));
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("GetDIBits failed on icon color bitmap"));
|
|
}
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = InvalidParameter;
|
|
}
|
|
|
|
if (iconInfo.hbmMask != NULL)
|
|
DeleteObject(iconInfo.hbmMask);
|
|
|
|
if (iconInfo.hbmColor != NULL)
|
|
DeleteObject(iconInfo.hbmColor);
|
|
}
|
|
else
|
|
{
|
|
status = InvalidParameter;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create CopyOnWriteBitmap from a resource.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* hInstance -- Specifies instance that contains resource
|
|
* lpBitmapName -- Specifies resource name or ordinal
|
|
* bitmap -- Return created bitmap via this buffer
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successful
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::CreateFromResource(
|
|
HINSTANCE hInstance,
|
|
LPWSTR lpBitmapName,
|
|
CopyOnWriteBitmap** bitmap
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
HBITMAP hbm = (HBITMAP) _LoadBitmap(hInstance, lpBitmapName);
|
|
|
|
if (hbm)
|
|
{
|
|
status = CreateFromHBITMAP(hbm, (HPALETTE) NULL, bitmap);
|
|
DeleteObject(hbm);
|
|
}
|
|
else
|
|
{
|
|
status = InvalidParameter;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* CopyOnWriteBitmap object destructor
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap::~CopyOnWriteBitmap()
|
|
{
|
|
this->FreeData();
|
|
|
|
// Close the encoder object attached to this
|
|
|
|
TerminateEncoder();
|
|
}
|
|
|
|
VOID
|
|
CopyOnWriteBitmap::FreeData()
|
|
{
|
|
GpFree(Filename);
|
|
if (Stream) Stream->Release();
|
|
if (Img) Img->Release();
|
|
if (Bmp) Bmp->Release();
|
|
if (InteropData.Hdc) DeleteDC(InteropData.Hdc);
|
|
if (InteropData.Hbm) DeleteObject(InteropData.Hbm);
|
|
if (cleanupBitmapData) GpFree(cleanupBitmapData);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Dereferences the stream or filename image into a non-decoded image.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* format - Specifies the preferred pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::DereferenceStream() const
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (State < DecodedImg)
|
|
{
|
|
ASSERT(Img == NULL);
|
|
|
|
if (State == ExtStream)
|
|
{
|
|
ASSERT(Stream != NULL);
|
|
hr = GpDecodedImage::CreateFromStream(Stream, &Img);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(State == ImageRef && Filename != NULL);
|
|
hr = GpDecodedImage::CreateFromFile(Filename, &Img);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
WARNING(("Failed to create decoded image: %x", hr));
|
|
State = Invalid;
|
|
return (MapHRESULTToGpStatus(hr));
|
|
}
|
|
|
|
State = DecodedImg;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Load the memory image into memory
|
|
*
|
|
* Arguments:
|
|
*
|
|
* format - Specifies the preferred pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::LoadIntoMemory(
|
|
PixelFormatID format,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData,
|
|
INT width,
|
|
INT height
|
|
) const
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if (State >= MemBitmap)
|
|
return Ok;
|
|
|
|
// Create decoded image object if necessary
|
|
|
|
HRESULT hr;
|
|
|
|
// Dereference the stream or file pointer and create an encoded image
|
|
// object that can be decoded by the codec.
|
|
// If the bitmap is already greater or equal to DecodedImg state, this
|
|
// is a nop.
|
|
|
|
GpStatus status = DereferenceStream();
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
ASSERT(Img != NULL);
|
|
|
|
if ( format == PixelFormatUndefined )
|
|
{
|
|
// If the caller doesn't care about the pixel format, then we load it
|
|
// as the source image format
|
|
|
|
format = SrcImageInfo.PixelFormat;
|
|
}
|
|
|
|
if ( ICMConvert == TRUE )
|
|
{
|
|
// Check if the OS supports ICM. We are doing this by checking if the
|
|
// ICM dlls we need are available on the system or not.
|
|
// Note: NT4 doesn't have ICM2 functionality. So the LoadICMDll() call
|
|
// should fail
|
|
// Note: LoadICMDll() is expensive only for the first time. If it has
|
|
// already beed loaded, then it is a very small cost
|
|
|
|
hr = LoadICMDll();
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// We should let the codec know that we need the native data format
|
|
// and we will do the conversion in ICMFrontEnd()
|
|
|
|
BOOL fUseICC = TRUE;
|
|
hr = Img->SetDecoderParam(DECODER_USEICC, 1, &fUseICC);
|
|
|
|
// Note: we don't need to check the return code of this call because
|
|
// it just pass the info down to the codec. If the codec doesn't
|
|
// support this. It is still fine
|
|
|
|
// If the source is in CMYK color space and we need to do ICM
|
|
// conversion, then we can't load the image in as 32PARGB. The reason
|
|
// is that the lower lever codec will return CMYK in native format,
|
|
// as we require it to. But if we ask
|
|
// GpMemoryBitmap::CreateFromImage() to create a 32PARGB, then it
|
|
// will do a format conversion and treat the C channel as ALPHA.
|
|
// That's completely wrong.
|
|
// So the solution here is to change the caller's requirement from
|
|
// 32PARGB to 32ARGB, remember it. Load the image in as 32ARGB, call
|
|
// ICMFrontEnd() to do the ICM conversion. Then before it is done,
|
|
// change the format back to 32 PARGB
|
|
// A complicated work around. MinLiu (01/25/2001)
|
|
|
|
if ( (format == PixelFormat32bppPARGB)
|
|
&&(SrcImageInfo.Flags & IMGFLAG_COLORSPACE_CMYK) )
|
|
{
|
|
HasChangedRequiredPixelFormat = TRUE;
|
|
format = PixelFormat32bppARGB;
|
|
}
|
|
}
|
|
|
|
// If the OS doesn't support ICM, then we don't set DECODER_USEICC and
|
|
// the codec will return RGB format to us
|
|
}
|
|
|
|
// Now load the image into memory
|
|
|
|
ASSERT(Bmp == NULL);
|
|
|
|
hr = GpMemoryBitmap::CreateFromImage(
|
|
Img,
|
|
width,
|
|
height,
|
|
format,
|
|
InterpolationHintAveraging,
|
|
&Bmp,
|
|
callback,
|
|
callbackData
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
WARNING(("Failed to load image into memory: %x", hr));
|
|
|
|
return MapHRESULTToGpStatus(hr);
|
|
}
|
|
|
|
// If resolution has been overridden, make sure GpMemoryBitmap is
|
|
// consistent with the set value
|
|
|
|
if ( (XDpiOverride > 0.0) && (YDpiOverride > 0.0) )
|
|
{
|
|
// Note: we don't need to check return code here since SetResolution()
|
|
// will not fail if both parameters are > 0
|
|
|
|
Bmp->SetResolution(XDpiOverride, YDpiOverride);
|
|
}
|
|
|
|
State = MemBitmap;
|
|
|
|
// Remember pixel format in the memory
|
|
|
|
PixelFormatInMem = format;
|
|
|
|
// We must be in MemBitmap state otherwise ICMFrontEnd will call us
|
|
// recursively.
|
|
ASSERT((State == MemBitmap));
|
|
|
|
// !!! ack - we have to call a huge chain of non-const stuff here from this
|
|
// const function. This should be fixed by removing the const from this
|
|
// function, but it's a pretty massive change.
|
|
const_cast<CopyOnWriteBitmap *>(this)->ICMFrontEnd(NULL, callback, callbackData, NULL);
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the encoder parameter list size from an encoder object specified by
|
|
* input clsid
|
|
*
|
|
* Arguments:
|
|
*
|
|
* clsidEncoder - Specifies the encoder class ID
|
|
* size---------- The size of the encoder parameter list
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 03/22/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetEncoderParameterListSize(
|
|
CLSID* clsidEncoder,
|
|
UINT* size
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
GpStatus status;
|
|
|
|
HRESULT hResult;
|
|
|
|
// If the image has a source and it is not dirty, we let the decoder
|
|
// directly talk to the encoder
|
|
|
|
if ( (Img != NULL) && (IsDirty() == FALSE) )
|
|
{
|
|
hResult = Img->GetEncoderParameterListSize(clsidEncoder, size);
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory();
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
hResult = Bmp->GetEncoderParameterListSize(clsidEncoder, size);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetEncoderParameterListSize()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the encoder parameter list from an encoder object specified by
|
|
* input clsid
|
|
*
|
|
* Arguments:
|
|
*
|
|
* clsidEncoder --- Specifies the encoder class ID
|
|
* size------------ The size of the encoder parameter list
|
|
* pBuffer--------- Buffer for storing the list
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 03/22/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetEncoderParameterList(
|
|
CLSID* clsidEncoder,
|
|
UINT size,
|
|
EncoderParameters* pBuffer
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
GpStatus status;
|
|
HRESULT hResult;
|
|
|
|
// If the image has a source and it is not dirty, we let the decoder
|
|
// directly talk to the encoder
|
|
|
|
if ( (Img != NULL) && (IsDirty() == FALSE) )
|
|
{
|
|
hResult = Img->GetEncoderParameterList(clsidEncoder, size, pBuffer);
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory();
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
hResult = Bmp->GetEncoderParameterList(clsidEncoder, size, pBuffer);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetEncoderParameterList()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Parse the input encoder parameter
|
|
*
|
|
* Arguments:
|
|
*
|
|
* encoderParams ------ Pointer to a set of encoder parameters
|
|
* pbIsMultiFrameSave--Return flag to tell the caller if this is a multi-frame
|
|
* saving operation or not
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Note:
|
|
* We don't validate input parameter because this is a private function.
|
|
* For performance reason the caller should validate the parameter before it
|
|
* calls this function. For the moment only those saving methods call it
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 07/19/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ParseEncoderParameter(
|
|
const EncoderParameters* encoderParams,
|
|
BOOL* pfIsMultiFrameSave,
|
|
BOOL* pfSpecialJPEG,
|
|
RotateFlipType* rfType
|
|
)
|
|
{
|
|
ASSERT(encoderParams != NULL);
|
|
ASSERT(pfIsMultiFrameSave != NULL);
|
|
ASSERT(pfSpecialJPEG != NULL);
|
|
ASSERT(rfType != NULL);
|
|
|
|
*pfIsMultiFrameSave = FALSE;
|
|
*pfSpecialJPEG = FALSE;
|
|
*rfType = RotateNoneFlipNone;
|
|
|
|
// Parse the encoder parameter caller set for:
|
|
// 1) Check if the caller has specified this is a multi-frame save OP or not
|
|
// 2) The caller can't set lossless transformation for JPEG if the image is
|
|
// dirty or the image size is not multiple of 16
|
|
|
|
for ( UINT i = 0; (i < encoderParams->Count); ++i )
|
|
{
|
|
if ( (encoderParams->Parameter[i].Guid == ENCODER_SAVE_FLAG)
|
|
&&(encoderParams->Parameter[i].Type == EncoderParameterValueTypeLong)
|
|
&&(encoderParams->Parameter[i].NumberOfValues == 1) )
|
|
{
|
|
UINT* pValue = (UINT*)encoderParams->Parameter[i].Value;
|
|
|
|
if ( *pValue == EncoderValueMultiFrame )
|
|
{
|
|
*pfIsMultiFrameSave = TRUE;
|
|
}
|
|
}
|
|
else if ( encoderParams->Parameter[i].Guid == ENCODER_TRANSFORMATION )
|
|
{
|
|
// We should check if the image format user wants to save is
|
|
// JPEG or not. But we can't do this since it might possible that
|
|
// other image codec supports "transformation". Also, we don't need
|
|
// to check this now since the codec will return "InvalidParameter"
|
|
// if it doesn't supports it.
|
|
//
|
|
// For transformation, the type has to be "ValueTypeLong" and
|
|
// "NumberOfValue" should be "1" because you can set only one
|
|
// transformation at a time
|
|
// Of course, the image has to be not dirty
|
|
|
|
if ( (encoderParams->Parameter[i].Type
|
|
!= EncoderParameterValueTypeLong)
|
|
||(encoderParams->Parameter[i].NumberOfValues != 1)
|
|
||(encoderParams->Parameter[i].Value == NULL)
|
|
||(IsDirty() == TRUE) )
|
|
{
|
|
WARNING(("COWBmap::ParseEncoderParameter-invalid input args"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if (SrcImageInfo.RawDataFormat == IMGFMT_JPEG)
|
|
{
|
|
// If the width or height is not multiple of 16, set it as a
|
|
// special JPEG so that we have to tranform it in memory
|
|
|
|
if (((SrcImageInfo.Width & 0x000F) != 0) ||
|
|
((SrcImageInfo.Height & 0x000F) != 0))
|
|
{
|
|
*pfSpecialJPEG = TRUE;
|
|
}
|
|
|
|
// If the source is JPEG, we will return "rfType" according to
|
|
// the encoder parameter
|
|
|
|
EncoderValue requiredTransform =
|
|
*((EncoderValue*)encoderParams->Parameter[i].Value);
|
|
|
|
switch ( requiredTransform )
|
|
{
|
|
case EncoderValueTransformRotate90:
|
|
*rfType = Rotate90FlipNone;
|
|
break;
|
|
|
|
case EncoderValueTransformRotate180:
|
|
*rfType = Rotate180FlipNone;
|
|
break;
|
|
|
|
case EncoderValueTransformRotate270:
|
|
*rfType = Rotate270FlipNone;
|
|
break;
|
|
|
|
case EncoderValueTransformFlipHorizontal:
|
|
*rfType = RotateNoneFlipX;
|
|
break;
|
|
|
|
case EncoderValueTransformFlipVertical:
|
|
*rfType = RotateNoneFlipY;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}// GUID == ENCODER_TRANSFORMATION
|
|
}// Loop all the settings
|
|
|
|
return Ok;
|
|
}// ParseEncoderParameter()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Transform embedded JPEG thumbnail so that it matches the transform applied
|
|
* to the main image.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Note:
|
|
* This function should be called iff and source image is JPEG and the caller
|
|
* wants to do a lossless transformation during save.
|
|
* Of course, if the source is not JPEG, this function won't do any harm to the
|
|
* result, just waste of time.
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 01/10/2002 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::TransformThumbanil(
|
|
IN CLSID* clsidEncoder, // CLSID for Destination format
|
|
IN EncoderParameters* encoderParams, // Encoder parameters
|
|
OUT PropertyItem **ppOriginalItem // Pointer to original thumbnail
|
|
// property item
|
|
)
|
|
{
|
|
if (ppOriginalItem == NULL)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if (NULL == encoderParams)
|
|
{
|
|
// Nothing we need to do
|
|
|
|
return Ok;
|
|
}
|
|
|
|
*ppOriginalItem = NULL;
|
|
|
|
Status status = Ok;
|
|
HRESULT hr = S_OK;
|
|
|
|
// The condition to transform the thumbnail are:
|
|
// 1) Source and dest are JPEGs. But we can't check the format here since it
|
|
// might have been transformed in memory due to the non-multiple of 16
|
|
// issue. The caller should control this, as said above.
|
|
// 2) Has a meaningful transform type
|
|
|
|
if (*clsidEncoder == InternalJpegClsID)
|
|
{
|
|
// Check if the source has thumbnail
|
|
|
|
UINT cSize = 0;
|
|
status = GetPropertyItemSize(PropertyTagThumbnailData, &cSize);
|
|
if (Ok == status)
|
|
{
|
|
// Allocate memory buffer for receiving it
|
|
|
|
PropertyItem *pItem = (PropertyItem*)GpMalloc(cSize);
|
|
if (pItem)
|
|
{
|
|
// Get the thumbnail data
|
|
|
|
status = GetPropertyItem(PropertyTagThumbnailData, cSize,pItem);
|
|
if (Ok == status)
|
|
{
|
|
GpImagingFactory imgFact;
|
|
GpDecodedImage *pThumbImage = NULL;
|
|
GpReadOnlyMemoryStream *pSrcStream =
|
|
new GpReadOnlyMemoryStream();
|
|
|
|
if (pSrcStream)
|
|
{
|
|
pSrcStream->InitBuffer(pItem->value, pItem->length);
|
|
|
|
// Create a decoded image object from the stream
|
|
|
|
hr = GpDecodedImage::CreateFromStream(
|
|
pSrcStream,
|
|
&pThumbImage
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Check the thumbnail size to see if it is multiple
|
|
// of 16 or not
|
|
|
|
ImageInfo imgInfo;
|
|
hr = pThumbImage->GetImageInfo(&imgInfo);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
BOOL fTrimEdge = FALSE;
|
|
|
|
// Number of encoder parameters to set
|
|
|
|
int cParams = 1;
|
|
|
|
if (((imgInfo.Width & 0x000F) != 0) ||
|
|
((imgInfo.Height & 0x000F) != 0))
|
|
{
|
|
// Do edge trim if it is non-multiple of 16
|
|
|
|
fTrimEdge = TRUE;
|
|
cParams++;
|
|
}
|
|
|
|
// Parameter index
|
|
|
|
int iParam = 0;
|
|
|
|
// Make up a transform encoder parameter
|
|
|
|
EncoderParameters *pThumbParam =
|
|
(EncoderParameters*)GpMalloc(
|
|
sizeof(EncoderParameters) +
|
|
cParams * sizeof(EncoderParameter));
|
|
UINT uTransformType = 0;
|
|
|
|
if (pThumbParam)
|
|
{
|
|
// Get the transform info from the main
|
|
// image's encoder parameter
|
|
|
|
for (UINT i = 0; i < (encoderParams->Count);
|
|
++i)
|
|
{
|
|
if (encoderParams->Parameter[i].Guid ==
|
|
ENCODER_TRANSFORMATION)
|
|
{
|
|
pThumbParam->Parameter[iParam].Guid=
|
|
ENCODER_TRANSFORMATION;
|
|
pThumbParam->Parameter[iParam].NumberOfValues =
|
|
encoderParams->Parameter[i].NumberOfValues;
|
|
pThumbParam->Parameter[iParam].Type=
|
|
encoderParams->Parameter[i].Type;
|
|
|
|
uTransformType =
|
|
*((UINT*)encoderParams->Parameter[i].Value);
|
|
pThumbParam->Parameter[iParam].Value =
|
|
&uTransformType;
|
|
|
|
iParam++;
|
|
|
|
// Only one transform parameter is
|
|
// allowed
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set the trim edge info if necessary
|
|
|
|
if (fTrimEdge)
|
|
{
|
|
BOOL trimFlag = TRUE;
|
|
|
|
pThumbParam->Parameter[iParam].Guid =
|
|
ENCODER_TRIMEDGE;
|
|
pThumbParam->Parameter[iParam].Type =
|
|
EncoderParameterValueTypeByte;
|
|
pThumbParam->Parameter[iParam].NumberOfValues
|
|
= 1;
|
|
pThumbParam->Parameter[iParam].Value = &trimFlag;
|
|
iParam++;
|
|
}
|
|
|
|
pThumbParam->Count = iParam;
|
|
|
|
// Create a memory stream for writing JPEG
|
|
|
|
GpWriteOnlyMemoryStream *pDestStream =
|
|
new GpWriteOnlyMemoryStream();
|
|
if (pDestStream)
|
|
{
|
|
// Set initiali buffer size to 2 times
|
|
// the source thumbnail image. This
|
|
// should be enough. On the other hand,
|
|
// GpWriteOnlyMemoryStream object will
|
|
// do realloc if necessary
|
|
|
|
hr = pDestStream->InitBuffer(
|
|
2 * pItem->length);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Save thumbnail to memory stream
|
|
|
|
IImageEncoder *pDstJpegEncoder =
|
|
NULL;
|
|
hr = pThumbImage->SaveToStream(
|
|
pDestStream,
|
|
clsidEncoder,
|
|
pThumbParam,
|
|
&pDstJpegEncoder
|
|
);
|
|
|
|
// Note: SaveToStream might fail.
|
|
// But the encoder might still be
|
|
// allocated before the failure.
|
|
// There are some code path
|
|
// limitations which causes this.
|
|
// Need to be revisited in Avalon.
|
|
// For now, we should release the
|
|
// encoder object if it is not NULL
|
|
|
|
if (pDstJpegEncoder)
|
|
{
|
|
pDstJpegEncoder->TerminateEncoder();
|
|
pDstJpegEncoder->Release();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Get the bits from the stream
|
|
// and set the property
|
|
|
|
BYTE *pRawBits = NULL;
|
|
UINT nLength = 0;
|
|
|
|
hr = pDestStream->GetBitsPtr(
|
|
&pRawBits,
|
|
&nLength
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
PropertyItem dstItem;
|
|
|
|
dstItem.id =
|
|
PropertyTagThumbnailData;
|
|
dstItem.length = nLength;
|
|
dstItem.type =
|
|
PropertyTagTypeByte;
|
|
dstItem.value = pRawBits;
|
|
|
|
status = SetPropertyItem(
|
|
&dstItem);
|
|
}
|
|
}// SaveToStream succeed
|
|
}// InitBuffer() succeed
|
|
|
|
pDestStream->Release();
|
|
}// Create GpWriteOnlyMemoryStream() succeed
|
|
|
|
GpFree(pThumbParam);
|
|
}// Allocate a encoder parameter block succeed
|
|
}// GetImageInfo succeed
|
|
|
|
pThumbImage->Release();
|
|
}// Create thumbImage succeed
|
|
|
|
pSrcStream->Release();
|
|
}// Create source stream
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}// Get thumbnail data
|
|
|
|
if ((Ok == status) && SUCCEEDED(hr))
|
|
{
|
|
// Pass the original thumbnail property item to the caller
|
|
// so that it can undo the transformation after the save()
|
|
|
|
*ppOriginalItem = pItem;
|
|
}
|
|
else
|
|
{
|
|
GpFree(pItem);
|
|
}
|
|
}// GpMalloc() succeed
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}// GetPropertyItemSize() Ok
|
|
|
|
if (PropertyNotFound == status)
|
|
{
|
|
// If we can't find thumbnail in the image, that's OK. We don't need
|
|
// to transform it. So this function should return Ok
|
|
|
|
status = Ok;
|
|
}
|
|
}// Condition check
|
|
|
|
if ((Ok == status) && FAILED(hr))
|
|
{
|
|
status = MapHRESULTToGpStatus(hr);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Validate if the encoder we created can really support multi-frame saving.
|
|
* If not, call TerminateEncoder()
|
|
* This method is called after we saved the image and we are not sure if we
|
|
* need to keep the encoder pointer.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* VOID
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Note:
|
|
* We don't validate input parameter because this is a private function.
|
|
* For performance reason the caller should validate the parameter before it
|
|
* calls this method. For the moment only those saving methods call it
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 07/19/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ValidateMultiFrameSave()
|
|
{
|
|
// Though the user sets the encoder parameter for multi-frame save, we
|
|
// still need to check if the lower level codec supports saving multi-
|
|
// frame or not.
|
|
// The reason we need to do this is that if the user sets this flag, we
|
|
// don't close the image file handle so that it can saving multi-frame.
|
|
// But for images like JPEG, it supports only single frame. If the user
|
|
// calls SaveAdd() subsequently, we will damage the file which has been
|
|
// saved with current Save() call
|
|
|
|
ASSERT(EncoderPtr != NULL);
|
|
|
|
UINT uiSize;
|
|
HRESULT hResult = EncoderPtr->GetEncoderParameterListSize(&uiSize);
|
|
|
|
if ( hResult == S_OK )
|
|
{
|
|
EncoderParameters* pParams = (EncoderParameters*)GpMalloc(uiSize);
|
|
|
|
if ( pParams == NULL )
|
|
{
|
|
// Though we are out of memory here. But we succeed in saving
|
|
// the image. So we should keep that result
|
|
|
|
WARNING(("CopyOnWriteBitmap::ValidateMultiFrameSave---Out of memory"));
|
|
TerminateEncoder();
|
|
|
|
return OutOfMemory;
|
|
}
|
|
|
|
hResult = EncoderPtr->GetEncoderParameterList(uiSize, pParams);
|
|
if ( hResult == S_OK )
|
|
{
|
|
// Check if the codec supports multi-frame save or not
|
|
|
|
UINT uiTemp;
|
|
|
|
for ( uiTemp = 0; (uiTemp < pParams->Count); ++uiTemp )
|
|
{
|
|
if ( (pParams->Parameter[uiTemp].Guid == ENCODER_SAVE_FLAG)
|
|
&&(pParams->Parameter[uiTemp].Type == EncoderParameterValueTypeLong)
|
|
&&(pParams->Parameter[uiTemp].NumberOfValues == 1)
|
|
&&(EncoderValueMultiFrame
|
|
== *((ULONG*)pParams->Parameter[uiTemp].Value) ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( uiTemp == pParams->Count )
|
|
{
|
|
// Not found clue for supporting multi-frame save
|
|
|
|
TerminateEncoder();
|
|
}
|
|
}
|
|
|
|
GpFree(pParams);
|
|
}
|
|
else
|
|
{
|
|
// This encoder doesn't provide encoder parameter query. It mustn't
|
|
// support multi-frame save
|
|
|
|
TerminateEncoder();
|
|
}
|
|
|
|
return Ok;
|
|
}// ValidateFrameSave()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Save the image to a stream using the specified encoder
|
|
*
|
|
* Arguments:
|
|
*
|
|
* stream - Specifies the target stream
|
|
* clsidEncoder - Specifies the CLSID of the encoder
|
|
* encoderParams - Parameters passed to the encoder
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SaveToStream(
|
|
IStream* stream,
|
|
CLSID* clsidEncoder,
|
|
EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
return DoSave(stream, NULL, clsidEncoder, encoderParams);
|
|
}// SaveToStream()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Save the image to a file using the specified encoder
|
|
*
|
|
* Arguments:
|
|
*
|
|
* stream - Specifies the filename to save to
|
|
* clsidEncoder - Specifies the CLSID of the encoder
|
|
* encoderParams - Parameters passed to the encoder
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SaveToFile(
|
|
const WCHAR* filename,
|
|
CLSID* clsidEncoder,
|
|
EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
return DoSave(NULL,filename,clsidEncoder,encoderParams);
|
|
}// SaveToFile()
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::DoSave(
|
|
IStream* stream,
|
|
const WCHAR* filename,
|
|
CLSID* clsidEncoder,
|
|
EncoderParameters* pEncoderParams
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// We already have an encoder attached to this bitmap. Need to close it
|
|
// first before we open a new one
|
|
|
|
TerminateEncoder();
|
|
|
|
GpStatus status = Ok;
|
|
HRESULT hr = S_OK;
|
|
|
|
BOOL fMultiFrameSave = FALSE;
|
|
BOOL fSpecialJPEG = FALSE;
|
|
RotateFlipType rfType = RotateNoneFlipNone;
|
|
|
|
if (pEncoderParams)
|
|
{
|
|
// Validate the encoder parameter caller set
|
|
|
|
status = ParseEncoderParameter(
|
|
pEncoderParams,
|
|
&fMultiFrameSave,
|
|
&fSpecialJPEG,
|
|
&rfType
|
|
);
|
|
|
|
if (status != Ok)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::DoSave--ParseEncoderParameter() failed"));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
// If the destination file format is JPEG and it needs special JPEG
|
|
// treatment, that is, the size doesn't meet the lossless transformation
|
|
// requirement. But the caller wants to do a lossless transformation. So we
|
|
// rotate or flip it in memory. Then pass this flag down to GpMemoryBitmap
|
|
// which will set the luminance and chrominance table before save. This way
|
|
// we can do our best to preserve the original JPEG image quality
|
|
|
|
if ((fSpecialJPEG == TRUE) &&
|
|
(rfType != RotateNoneFlipNone) &&
|
|
(*clsidEncoder == InternalJpegClsID))
|
|
{
|
|
// We are handling special lossless JPEG transform saving request
|
|
|
|
SpecialJPEGSave = TRUE;
|
|
|
|
// Rotate or flip in memory.
|
|
|
|
hr = RotateFlip(rfType);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::DoSave-RotateFlip() failed"));
|
|
return MapHRESULTToGpStatus(hr);
|
|
}
|
|
}
|
|
|
|
// If the image has a source and it is not dirty, we let the decoder
|
|
// directly talk to the encoder
|
|
|
|
PropertyItem *pSrcItem = NULL;
|
|
BOOL fNeedToRestoreThumb = FALSE;
|
|
|
|
if ((Img != NULL) && (IsDirty() == FALSE))
|
|
{
|
|
// Since we can't save CMYK TIFF for now. So we shouldn't pass CMYK bits
|
|
// to the encoder. JPEG decoder doesn't support this decoder parameter
|
|
// yet. See windows bug#375298 for more details.
|
|
// In V2, after we add CMYK as one of the color format, after we move
|
|
// all the color conversion stuff to an approprite place, we will
|
|
// re-visit the code here.
|
|
|
|
BOOL fUseICC = FALSE;
|
|
hr = Img->SetDecoderParam(DECODER_USEICC, 1, &fUseICC);
|
|
|
|
// Note: we don't need to check the return code for SetDecoderParam()
|
|
// Most codec not support it. Then it will be a Nop.
|
|
|
|
// Handle thumbnail transformation if it is lossless JPEG transformation
|
|
// Note: rfType will be set to a non-RotateNoneFlipNone value iff the
|
|
// source image is JPEG
|
|
|
|
if (rfType != RotateNoneFlipNone)
|
|
{
|
|
status = TransformThumbanil(clsidEncoder,pEncoderParams, &pSrcItem);
|
|
}
|
|
|
|
if (Ok == status)
|
|
{
|
|
fNeedToRestoreThumb = TRUE;
|
|
|
|
if (stream)
|
|
{
|
|
hr = Img->SaveToStream(
|
|
stream,
|
|
clsidEncoder,
|
|
pEncoderParams,
|
|
&EncoderPtr
|
|
);
|
|
}
|
|
else if (filename)
|
|
{
|
|
hr = Img->SaveToFile(
|
|
filename,
|
|
clsidEncoder,
|
|
pEncoderParams,
|
|
&EncoderPtr
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This should not happen that both stream and filename are NULL
|
|
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory();
|
|
|
|
if (status != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
EncoderParameters *pNewParam = pEncoderParams;
|
|
BOOL fFreeExtraParamBlock = FALSE;
|
|
|
|
// PAY attention to the scope of "fSuppressAPP0". Its address is used as
|
|
// parameter passed into Save() call below. So this variable can't be
|
|
// destroyed before the save() is called.
|
|
|
|
BOOL fSuppressAPP0 = TRUE;
|
|
|
|
if (fSpecialJPEG == TRUE)
|
|
{
|
|
// We are in a situation that the caller asks us to do a lossless
|
|
// transformation. Due to the size limitation, we have to
|
|
// transform it in memory.
|
|
// Since it is not correct to save APP0 in a Exif file. So we check
|
|
// if the source is Exif, then we suppress APP0 header
|
|
|
|
int cParams = 1;
|
|
|
|
pNewParam = (EncoderParameters*)GpMalloc(
|
|
sizeof(EncoderParameters) +
|
|
cParams * sizeof(EncoderParameter));
|
|
|
|
if (pNewParam)
|
|
{
|
|
// Set the Suppress APP0 parameter
|
|
|
|
pNewParam->Parameter[cParams - 1].Guid = ENCODER_SUPPRESSAPP0;
|
|
pNewParam->Parameter[cParams - 1].NumberOfValues = 1;
|
|
pNewParam->Parameter[cParams - 1].Type = TAG_TYPE_BYTE;
|
|
pNewParam->Parameter[cParams - 1].Value = (VOID*)&fSuppressAPP0;
|
|
|
|
pNewParam->Count = cParams;
|
|
|
|
// Set the flag to TRUE so that we can free it later
|
|
|
|
fFreeExtraParamBlock = TRUE;
|
|
|
|
// Handle thumbnail transformation if it is lossless
|
|
// transformation
|
|
|
|
if (rfType != RotateNoneFlipNone)
|
|
{
|
|
status = TransformThumbanil(
|
|
clsidEncoder,
|
|
pEncoderParams,
|
|
&pSrcItem
|
|
);
|
|
|
|
if (Ok == status)
|
|
{
|
|
fNeedToRestoreThumb = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
}// Special JPEG case
|
|
|
|
if (SUCCEEDED(hr) && (Ok == status))
|
|
{
|
|
// Determine how should we pass the GpDecodedImage pointer down to
|
|
// the save() call. If we are handling special lossless transform,
|
|
// we know that we have RotateFlip() the image in memory, so we
|
|
// should not pass any GpDecodedImage info down, just pass NULL.
|
|
// Otherwise, pass the pointer to GpDecodedImage down.
|
|
|
|
GpDecodedImage *pSrc = Img;
|
|
if (SpecialJPEGSave == TRUE)
|
|
{
|
|
pSrc = NULL;
|
|
}
|
|
|
|
if (stream)
|
|
{
|
|
hr = Bmp->SaveToStream(
|
|
stream,
|
|
clsidEncoder,
|
|
pNewParam,
|
|
fSpecialJPEG,
|
|
&EncoderPtr,
|
|
pSrc
|
|
);
|
|
}
|
|
else if (filename)
|
|
{
|
|
hr = Bmp->SaveToFile(
|
|
filename,
|
|
clsidEncoder,
|
|
pNewParam,
|
|
fSpecialJPEG,
|
|
&EncoderPtr,
|
|
pSrc
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This should not happen that both stream and filename are NULL
|
|
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
// When we handle the special lossless transform request, we rotate/flip
|
|
// the image in memory. The Img pointer should be released when
|
|
// RotateFlip() is done. But we couldn't do that since Save() function
|
|
// in JPEG encoder needs to get all the private APP headers from the
|
|
// source image. So we delay the release for the Img pointer until the
|
|
// save() is done.
|
|
|
|
if (Img && (SpecialJPEGSave == TRUE))
|
|
{
|
|
Img->Release();
|
|
Img = NULL;
|
|
SpecialJPEGSave = FALSE;
|
|
}
|
|
|
|
if (fFreeExtraParamBlock && pNewParam)
|
|
{
|
|
GpFree(pNewParam);
|
|
}
|
|
}
|
|
|
|
if ((TRUE == fNeedToRestoreThumb) && pSrcItem)
|
|
{
|
|
// If pSrcIetm is not NULL, it means we have transformed the thumbnail
|
|
// of current image. Restore the original thumbnail info
|
|
|
|
status = SetPropertyItem(pSrcItem);
|
|
GpFree(pSrcItem);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// If SaveToFile/Stream() filed, we should terminate the encoder
|
|
// immediately.
|
|
// We don't need to check if it is multi-frame save or not.
|
|
|
|
TerminateEncoder();
|
|
return MapHRESULTToGpStatus(hr);
|
|
}
|
|
|
|
// If it is a single frame save OP, close the encoder
|
|
|
|
if (fMultiFrameSave == FALSE)
|
|
{
|
|
TerminateEncoder();
|
|
}
|
|
else
|
|
{
|
|
// The caller set the multi-frame save flag in encoder parameter. But
|
|
// we still need to check if the encoder really supports it. If not,
|
|
// the encoder will be closed in ValidateMultiFrameSave()
|
|
|
|
ValidateMultiFrameSave();
|
|
}
|
|
|
|
return status;
|
|
}// DoSave()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Append current frame to current encoder object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* encoderParams - Encoder parameters
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 04/21/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SaveAdd(
|
|
const EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
// Caller has to call Save() first to establish the encoder object
|
|
|
|
if ( EncoderPtr == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SaveAdd---Caller hasn't call Save() yet"));
|
|
return Win32Error;
|
|
}
|
|
|
|
// We don't need to check if encoderParams is NULL or not because it has
|
|
// been checked in flatapi.cpp
|
|
|
|
ASSERT(encoderParams != NULL);
|
|
ASSERT(IsValid());
|
|
|
|
BOOL bLastFrame = FALSE;
|
|
BOOL bSetFrameDimension = FALSE;
|
|
GUID tempGuid;
|
|
|
|
// Check if the caller has specified this is the last frame or a flush OP
|
|
// Also, according to spec, the caller also has to specify the type of
|
|
// dimension for next frame
|
|
|
|
for ( UINT i = 0; (i < encoderParams->Count); ++i )
|
|
{
|
|
if ( (encoderParams->Parameter[i].Guid == ENCODER_SAVE_FLAG )
|
|
&&(encoderParams->Parameter[i].Type == EncoderParameterValueTypeLong)
|
|
&&(encoderParams->Parameter[i].NumberOfValues == 1) )
|
|
{
|
|
UINT ulValue = *((UINT*)(encoderParams->Parameter[i].Value));
|
|
|
|
if ( ulValue == EncoderValueLastFrame )
|
|
{
|
|
bLastFrame = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFlush )
|
|
{
|
|
// The caller just wants to close the file
|
|
|
|
TerminateEncoder();
|
|
|
|
return Ok;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionPage )
|
|
{
|
|
tempGuid = FRAMEDIM_PAGE;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionTime )
|
|
{
|
|
tempGuid = FRAMEDIM_TIME;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionResolution )
|
|
{
|
|
tempGuid = FRAMEDIM_RESOLUTION;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
}
|
|
}// Loop all the settings
|
|
|
|
HRESULT hResult = S_OK;
|
|
GpStatus status;
|
|
|
|
if ( bSetFrameDimension == FALSE )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SaveAdd---Caller doesn't set frame dimension"));
|
|
return InvalidParameter;
|
|
}
|
|
else
|
|
{
|
|
hResult = EncoderPtr->SetFrameDimension(&tempGuid);
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}
|
|
}
|
|
|
|
// If the image has a source and it is not dirty, we let the decoder
|
|
// directly talk to the encoder
|
|
|
|
if ( (Img != NULL) && (IsDirty() == FALSE) )
|
|
{
|
|
hResult = Img->SaveAppend(encoderParams, EncoderPtr);
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory();
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
hResult = Bmp->SaveAppend(encoderParams, EncoderPtr, Img);
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}
|
|
|
|
// If it is the last frame, close the encoder
|
|
|
|
if ( bLastFrame == TRUE )
|
|
{
|
|
TerminateEncoder();
|
|
}
|
|
|
|
return Ok;
|
|
}// SaveAdd()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Append the bitmap object(newBits) to current encoder object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* newBits-------- Image object to be appended
|
|
* encoderParams - Encoder parameters
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 04/21/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SaveAdd(
|
|
CopyOnWriteBitmap* newBits,
|
|
const EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
// Caller has to call Save() first to establish the encoder object
|
|
|
|
if ( EncoderPtr == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SaveAdd---Caller hasn't call Save() yet"));
|
|
return Win32Error;
|
|
}
|
|
|
|
// Note: we don't need to check if "newBits" is NULL and encoderParams is
|
|
// NULL since it has been checked in flatapi.cpp
|
|
|
|
ASSERT(newBits != NULL);
|
|
ASSERT(encoderParams != NULL);
|
|
ASSERT(IsValid());
|
|
|
|
BOOL bLastFrame = FALSE;
|
|
BOOL bSetFrameDimension = FALSE;
|
|
GUID tempGuid;
|
|
|
|
// Check if the caller has specified this is the last frame
|
|
// Also, according to spec, the caller also has to specify the type of
|
|
// dimension for next frame
|
|
|
|
for ( UINT i = 0; (i < encoderParams->Count); ++i )
|
|
{
|
|
if ( (encoderParams->Parameter[i].Guid == ENCODER_SAVE_FLAG)
|
|
&&(encoderParams->Parameter[i].Type == EncoderParameterValueTypeLong)
|
|
&&(encoderParams->Parameter[i].NumberOfValues == 1) )
|
|
{
|
|
UINT ulValue = *((UINT*)(encoderParams->Parameter[i].Value));
|
|
|
|
if ( ulValue == EncoderValueLastFrame )
|
|
{
|
|
bLastFrame = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionPage )
|
|
{
|
|
tempGuid = FRAMEDIM_PAGE;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionTime )
|
|
{
|
|
tempGuid = FRAMEDIM_TIME;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
else if ( ulValue == EncoderValueFrameDimensionResolution )
|
|
{
|
|
tempGuid = FRAMEDIM_RESOLUTION;
|
|
bSetFrameDimension = TRUE;
|
|
}
|
|
}
|
|
}// Loop all the settings
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( bSetFrameDimension == FALSE )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SaveAdd---Caller doesn't set frame dimension"));
|
|
return InvalidParameter;
|
|
}
|
|
else
|
|
{
|
|
hResult = EncoderPtr->SetFrameDimension(&tempGuid);
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return (MapHRESULTToGpStatus(hResult));
|
|
}
|
|
}
|
|
|
|
// We just need to call newBits->SaveAppend() and passing the EncoderPtr to
|
|
// it. newBits->SaveAppend() should append all the frames in the object at
|
|
// the end of the stream pointed by EncoderPtr
|
|
|
|
CopyOnWriteBitmap* newBitmap = (CopyOnWriteBitmap*)newBits;
|
|
Status rCode = newBitmap->SaveAppend(encoderParams, EncoderPtr);
|
|
|
|
// If it is the last frame, close the encoder
|
|
|
|
if ( bLastFrame == TRUE )
|
|
{
|
|
TerminateEncoder();
|
|
}
|
|
|
|
return rCode;
|
|
}// SaveAdd()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Append current frame to the encoder object caller passed in
|
|
* Note: This function is called from another CopyOnWriteBitmap object which holds the
|
|
* encoder object. It asks current frame to be appended at the end of its
|
|
* encoder object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* encoderParams - Encoder parameters
|
|
* pDestEncoderPtr---Encoder object for saving this frame to
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 04/21/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SaveAppend(
|
|
const EncoderParameters* encoderParams,
|
|
IImageEncoder* pDestEncoderPtr
|
|
)
|
|
{
|
|
// We don't need to check if EncoderPtr is NULL since this is not a public
|
|
// function. The caller should check
|
|
|
|
ASSERT(pDestEncoderPtr != NULL);
|
|
|
|
ASSERT(IsValid());
|
|
|
|
HRESULT hResult;
|
|
GpStatus status;
|
|
|
|
// If the image has a source and it is not dirty, we let the decoder
|
|
// directly talk to the encoder
|
|
|
|
if ( (Img != NULL) && (IsDirty() == FALSE) )
|
|
{
|
|
hResult = Img->SaveAppend(encoderParams, pDestEncoderPtr);
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory();
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
hResult = Bmp->SaveAppend(encoderParams, pDestEncoderPtr, Img);
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return (MapHRESULTToGpStatus(hResult));
|
|
}
|
|
|
|
return Ok;
|
|
}// SaveAppend()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Make a copy of the bitmap image object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* rect - Specifies the area of the bitmap to be copied
|
|
* format - Specifies the desired pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the newly copied bitmap image object
|
|
* NULL if there is an error
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 06/30/2000 minliu
|
|
* Rewrote it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap*
|
|
CopyOnWriteBitmap::Clone(
|
|
const GpRect* rect,
|
|
PixelFormatID format
|
|
) const
|
|
{
|
|
// At this stage, the state should be >= 3 for a CopyOnWriteBitmap
|
|
|
|
ASSERT(State >= 3);
|
|
|
|
// Input parameter validate
|
|
|
|
if ( (rect != NULL)
|
|
&&( (rect->X < 0)
|
|
||(rect->Y < 0)
|
|
||(rect->Width < 0)
|
|
||(rect->Height < 0) ) )
|
|
{
|
|
// We can't clone negative coordinates or size
|
|
|
|
WARNING(("CopyOnWriteBitmap::Clone---invalid input rect"));
|
|
return NULL;
|
|
}
|
|
|
|
if ( (rect != NULL)
|
|
&&( ( (rect->X + rect->Width) > (INT)SrcImageInfo.Width)
|
|
||( (rect->Y + rect->Height) > (INT)SrcImageInfo.Height) ) )
|
|
{
|
|
// We can't clone an area which is bigger than the source image
|
|
|
|
WARNING(("CopyOnWriteBitmap::Clone---invalid input rect size"));
|
|
return NULL;
|
|
}
|
|
|
|
if ( format == PixelFormatUndefined )
|
|
{
|
|
// If the caller doesn't care about the pixel format, then we clone it
|
|
// as the source image format
|
|
// Note: This is the most frequently used the scenario since we have
|
|
// Image::Clone() which doesn't take any parameters.
|
|
// And we have
|
|
// GpImage* Clone() const
|
|
// {
|
|
// return Clone(NULL, PixelFormatDontCare);
|
|
// }
|
|
|
|
format = SrcImageInfo.PixelFormat;
|
|
}
|
|
|
|
CopyOnWriteBitmap* pRetBmp = NULL;
|
|
|
|
// Flag to indicate if we need to undo the LoadIntoMemory() or not
|
|
// Note: This flag will be set to TRUE iff the current State is "DecodedImg"
|
|
// and this function does a LoadIntoMemory()
|
|
|
|
BOOL bNeedToDiscard = FALSE;
|
|
|
|
// A non-identical clone will happen if:
|
|
// 1) the caller wants clone only a portion of the source image
|
|
// 2) the dest image has a different pixel format than the current one
|
|
// A non-identical clone means the newly created image doesn't have any
|
|
// connections to the original image in terms of FileName or Stream etc.
|
|
// Note: For a non-identical clone, we don't clone the property items either
|
|
|
|
BOOL bIdenticalClone = TRUE;
|
|
|
|
if ( (rect != NULL)
|
|
&&( (rect->X != 0)
|
|
|| (rect->Y != 0)
|
|
|| (rect->Width != (INT)SrcImageInfo.Width)
|
|
|| (rect->Height != (INT)SrcImageInfo.Height)
|
|
|| (SrcImageInfo.PixelFormat != format) ) )
|
|
{
|
|
bIdenticalClone = FALSE;
|
|
}
|
|
|
|
// If the image is:
|
|
// 1) Not dirty
|
|
// 2) We have an source image
|
|
// 3) The image has been loaded into memory
|
|
//
|
|
// Then we need to throw away the memory copy. The reasons are:
|
|
// 1) Avoid the color conversion failure. One example will be: if the
|
|
// source image is 1 bpp indexed and we load it into memory at 32 PARGB
|
|
// for drawing. If we don't throw away the 32PARGB copy in memory, we
|
|
// will fail the clone() because the color conversion will fail
|
|
// 2) Keep property item intact. for example, if the image is 24 bpp with
|
|
// property items in it. But it was loaded into memory for some reason.
|
|
// If we don't throw away the memory copy here, the code below will fall
|
|
// into "else if ( State == MemBitmap )" case. Then it will call
|
|
// Bmp->Clone() to make another copy in memory. Since the source "Bmp"
|
|
// doesn't contain any property info. The cloned one won't have any
|
|
// property info either. See Windows bug#325413
|
|
// 3) If current image is "Dirty", we don't need to keep property items.
|
|
|
|
if ( (IsDirty() == FALSE)
|
|
&&(State >= MemBitmap)
|
|
&&(Img != NULL) )
|
|
{
|
|
ASSERT( Bmp != NULL )
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
State = DecodedImg;
|
|
PixelFormatInMem = PixelFormatUndefined;
|
|
}
|
|
|
|
// We have to clone the image in memory if it is not an identical clone.
|
|
// So if the image hasn't been loaded into memory yet, load it
|
|
|
|
if ( (State == DecodedImg)
|
|
&&(FALSE == bIdenticalClone) )
|
|
{
|
|
// Note: Due to some general pixel format conversion limitation in the
|
|
// whole Engine, we try to avoid doing LoadIntoMemory(). Hopefully this
|
|
// will be fixed sometime later. So I add a !!!TODO {minliu} here.
|
|
// But for now, we have to load the current image into memory with the
|
|
// desired pixel format and throw it away when we are done.
|
|
|
|
if ( LoadIntoMemory(format) != Ok )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::Clone---LoadIntoMemory() failed"));
|
|
return NULL;
|
|
}
|
|
|
|
bNeedToDiscard = TRUE;
|
|
}
|
|
|
|
// Do clone according to the current Image State
|
|
|
|
if ( State == DecodedImg )
|
|
{
|
|
// Current source image hasn't been loaded and the caller wants to
|
|
// clone the WHOLE image
|
|
// Note: there are only two ways to construct a CopyOnWriteBitmap object
|
|
// and with the State = DecodedImg: CopyOnWriteBitmap(IStream*) and
|
|
// CopyOnWriteBitmap(WCHAR*). So what we need to do is to create a
|
|
// CopyOnWriteBitmap object by calling the same constructor
|
|
|
|
if ( this->Filename != NULL )
|
|
{
|
|
pRetBmp = new CopyOnWriteBitmap(this->Filename);
|
|
if ( pRetBmp == NULL )
|
|
{
|
|
WARNING(("CopyOnWrite::Clone--new CopyOnWriteBitmap() failed"));
|
|
return NULL;
|
|
}
|
|
}
|
|
else if ( this->Stream != NULL )
|
|
{
|
|
pRetBmp = new CopyOnWriteBitmap(this->Stream);
|
|
|
|
if ( pRetBmp == NULL )
|
|
{
|
|
WARNING(("CopyOnWrite::Clone--new CopyOnWriteBitmap() failed"));
|
|
return NULL;
|
|
}
|
|
}
|
|
}// State == DecodedImg
|
|
else if ( State == MemBitmap )
|
|
{
|
|
// Current source image has already been loaded into memory
|
|
// Note: the above checking (State == MemBitmap) might not be necessary.
|
|
// But we leave it here just to prevent someone adds another state in
|
|
// the State enum later.
|
|
|
|
IBitmapImage* newbmp = NULL;
|
|
HRESULT hResult;
|
|
|
|
if ( rect == NULL )
|
|
{
|
|
hResult = Bmp->Clone(NULL, &newbmp, bIdenticalClone);
|
|
}
|
|
else
|
|
{
|
|
RECT r =
|
|
{
|
|
rect->X,
|
|
rect->Y,
|
|
rect->GetRight(),
|
|
rect->GetBottom()
|
|
};
|
|
|
|
hResult = Bmp->Clone(&r, &newbmp, bIdenticalClone);
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::Clone---Bmp->clone() failed"));
|
|
goto cleanup;
|
|
}
|
|
|
|
// !!! TODO
|
|
// We assume that IBitmapImage is the very first
|
|
// interface implemented by GpMemoryBitmap class.
|
|
|
|
pRetBmp = new CopyOnWriteBitmap((GpMemoryBitmap*)newbmp);
|
|
|
|
if ( pRetBmp == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBmp::Clone---new CopyOnWriteBitmap() failed"));
|
|
newbmp->Release();
|
|
goto cleanup;
|
|
}
|
|
|
|
// Clone the source info as well if it is an identical clone
|
|
|
|
if ( TRUE == bIdenticalClone )
|
|
{
|
|
if ( this->Filename != NULL )
|
|
{
|
|
pRetBmp->Filename = UnicodeStringDuplicate(this->Filename);
|
|
}
|
|
else if ( this->Stream != NULL )
|
|
{
|
|
pRetBmp->Stream = this->Stream;
|
|
pRetBmp->Stream->AddRef();
|
|
}
|
|
}
|
|
|
|
// Make sure clone has requested format. The reason we need to do this
|
|
// is because the source image might have different pixel format as the
|
|
// caller wants. This would be caused someone already did an
|
|
// LoadIntoMemory() call on current object before this clone() is called
|
|
|
|
PixelFormatID formatRetbmp;
|
|
|
|
GpStatus status = pRetBmp->GetPixelFormatID(&formatRetbmp);
|
|
|
|
if ( (status == Ok) && (format != formatRetbmp) )
|
|
{
|
|
status = pRetBmp->ConvertFormat(format, NULL, NULL);
|
|
}
|
|
|
|
if ( status != Ok )
|
|
{
|
|
WARNING(("CopyOnWrite:Clone-GetPixelFormatID() or Convert failed"));
|
|
pRetBmp->Dispose();
|
|
pRetBmp = NULL;
|
|
}
|
|
}// State == MemBitmap
|
|
|
|
cleanup:
|
|
if ( bNeedToDiscard == TRUE )
|
|
{
|
|
// Throw away the memory bits we loaded in this function and restore
|
|
// the State
|
|
|
|
if ( Bmp != NULL )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
State = DecodedImg;
|
|
}
|
|
}
|
|
|
|
// We need to check if the result of the clone is valid or not. If it is
|
|
// not valid, we should return a NULL pointer
|
|
|
|
if ( (pRetBmp != NULL) && (!pRetBmp->IsValid()) )
|
|
{
|
|
pRetBmp->Dispose();
|
|
pRetBmp = NULL;
|
|
}
|
|
|
|
if (pRetBmp)
|
|
{
|
|
// copy internal state into the new CopyOnWriteBitmap.
|
|
|
|
pRetBmp->ICMConvert = ICMConvert;
|
|
}
|
|
|
|
return pRetBmp;
|
|
}// Clone()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set the palette for this bitmap
|
|
*
|
|
* Arguments:
|
|
*
|
|
* OUT palette - contains the palette.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetPalette(
|
|
ColorPalette *palette
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
GpStatus status;
|
|
|
|
switch(State) {
|
|
case ImageRef:
|
|
case ExtStream:
|
|
status = DereferenceStream();
|
|
if(status != Ok) { return status; }
|
|
// Put the image in at least DecodedImg state and
|
|
// fallthrough
|
|
|
|
case DecodedImg:
|
|
// Get the info from the encoded image without forcing the codec
|
|
// to decode the entire thing.
|
|
|
|
// !!! TODO: Actually we don't yet have a way of setting the palette
|
|
// directly from the codec.
|
|
|
|
status = LoadIntoMemory(PIXFMT_DONTCARE);
|
|
|
|
// Load into memory failed? Return the error code.
|
|
|
|
if(status != Ok) { return status; }
|
|
|
|
// !!! break; Fallthrough for now - till we get the codec query implemented
|
|
|
|
case MemBitmap:
|
|
{
|
|
// We're already fully decoded, just set the information.
|
|
|
|
HRESULT hr = Bmp->SetPalette(palette);
|
|
|
|
// Did we fail to set the palette?
|
|
|
|
if(hr != S_OK)
|
|
{
|
|
return GenericError;
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// All image states need to be handled above.
|
|
// If we get in here, we have a CopyOnWriteBitmap in an invalid state or
|
|
// someone added a new state and needs to update the switch above.
|
|
ASSERT(FALSE);
|
|
return InvalidParameter;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the palette for this bitmap
|
|
*
|
|
* Arguments:
|
|
*
|
|
* OUT palette - contains the palette.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPalette(
|
|
ColorPalette *palette,
|
|
INT size
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
ASSERT(palette != NULL); // need a buffer to store the data in.
|
|
|
|
if ( size < sizeof(ColorPalette) )
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
GpStatus status;
|
|
|
|
switch(State) {
|
|
case ImageRef:
|
|
case ExtStream:
|
|
status = DereferenceStream();
|
|
if(status != Ok) { return status; }
|
|
// Put the image in at least DecodedImg state and
|
|
// fallthrough
|
|
|
|
case DecodedImg:
|
|
// Get the info from the encoded image without forcing the codec
|
|
// to decode the entire thing.
|
|
|
|
// !!! TODO: Actually we don't yet have a way of getting the palette
|
|
// directly from the codec.
|
|
|
|
status = LoadIntoMemory(PIXFMT_DONTCARE);
|
|
|
|
// Load into memory failed? Return the error code.
|
|
|
|
if(status != Ok) { return status; }
|
|
|
|
// !!! break; Fallthrough for now - till we get the codec query implemented
|
|
|
|
case MemBitmap:
|
|
{
|
|
// We're already fully decoded, just get the information.
|
|
const ColorPalette *pal = Bmp->GetCurrentPalette();
|
|
if(pal)
|
|
{
|
|
// Make sure the size is correct.
|
|
if(size != (INT) (sizeof(ColorPalette)+(pal->Count-1)*sizeof(ARGB)) )
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
// Copy the palette into the user buffer.
|
|
GpMemcpy(palette, pal, sizeof(ColorPalette)+(pal->Count-1)*sizeof(ARGB));
|
|
}
|
|
else
|
|
{
|
|
// If there is no palette, we need to properly set the
|
|
// ColorPalette structure.
|
|
|
|
palette->Count = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// All image states need to be handled above.
|
|
// If we get in here, we have a CopyOnWriteBitmap in an invalid state or
|
|
// someone added a new state and needs to update the switch above.
|
|
ASSERT(FALSE);
|
|
return InvalidParameter;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Returns the size, in bytes, needed for holding a palette for this bitmap
|
|
*
|
|
* Arguments:
|
|
*
|
|
* Return Value:
|
|
*
|
|
* The size, in bytes. Return 0 if there is no palette or something is wrong
|
|
*
|
|
* Note: should return -1 for something wrong.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
INT
|
|
CopyOnWriteBitmap::GetPaletteSize(
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
GpStatus status;
|
|
|
|
switch(State)
|
|
{
|
|
case ImageRef:
|
|
case ExtStream:
|
|
status = DereferenceStream();
|
|
|
|
if(status != Ok)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Put the image in at least DecodedImg state and
|
|
// fallthrough
|
|
|
|
case DecodedImg:
|
|
// Get the info from the encoded image without forcing the codec
|
|
// to decode the entire thing.
|
|
|
|
// !!! TODO: Actually we don't yet have a way of getting the palette
|
|
// directly from the codec.
|
|
|
|
status = LoadIntoMemory(PIXFMT_DONTCARE);
|
|
|
|
// Load into memory failed? Return a zero palette size.
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// !!! break; Fallthrough for now - till we get the codec query implemented
|
|
|
|
case MemBitmap:
|
|
{
|
|
// We're already fully decoded, just get the information.
|
|
|
|
const ColorPalette *pal = Bmp->GetCurrentPalette();
|
|
|
|
// Extract the size.
|
|
|
|
if(pal)
|
|
{
|
|
return (sizeof(ColorPalette)+(pal->Count-1)*sizeof(ARGB));
|
|
}
|
|
else
|
|
{
|
|
// Note: if the image doesn't have a palette, we should still
|
|
// return at least the size of a ColorPalette, not zero here.
|
|
// The reason is to prevent some bad app which can cause GDI+'s
|
|
// GetPalette() to AV, see bug#372163
|
|
|
|
return sizeof(ColorPalette);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// All image states need to be handled above.
|
|
// If we get in here, we have a CopyOnWriteBitmap in an invalid state or
|
|
// someone added a new state and needs to update the switch above.
|
|
|
|
ASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Returns total number of frames in the bitmap image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* dimensionID - Dimension GUID the caller wants to query the count
|
|
* count - Total number of frames under specified dimension
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 11/19/1999 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetFrameCount(
|
|
const GUID* dimensionID,
|
|
UINT* count
|
|
) const
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if ( Img == NULL )
|
|
{
|
|
// This CopyOnWriteBitmap is not created from a source image. It doesn't
|
|
// have source Stream, nor source file name. It might be created
|
|
// from a BITMAPINFO structure or a memory bitmap. But anyway, it has
|
|
// one frame. So we return 1 here.
|
|
|
|
*count = 1;
|
|
return Ok;
|
|
}
|
|
|
|
HRESULT hResult = Img->GetFrameCount(dimensionID, count);
|
|
|
|
if ( hResult == E_NOTIMPL )
|
|
{
|
|
return NotImplemented;
|
|
}
|
|
else if ( hResult != S_OK )
|
|
{
|
|
return Win32Error;
|
|
}
|
|
|
|
return Ok;
|
|
}// GetFrameCount()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the total number of dimensions the image supports
|
|
*
|
|
* Arguments:
|
|
*
|
|
* count -- number of dimensions this image format supports
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 03/20/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetFrameDimensionsCount(
|
|
UINT* count
|
|
) const
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if ( count == NULL )
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if ( Img == NULL )
|
|
{
|
|
// This CopyOnWriteBitmap is not created from a source image. It doesn't
|
|
// have source Stream, nor source file name. It might be created
|
|
// from a BITMAPINFO structure or a memory bitmap. But anyway, it has
|
|
// one PAGE frame. So we set the return values accordingly.
|
|
|
|
*count = 1;
|
|
|
|
return Ok;
|
|
}
|
|
|
|
// Ask the lower level codec to give us the answer
|
|
|
|
HRESULT hResult = Img->GetFrameDimensionsCount(count);
|
|
|
|
if ( hResult == E_NOTIMPL )
|
|
{
|
|
return NotImplemented;
|
|
}
|
|
else if ( hResult != S_OK )
|
|
{
|
|
return Win32Error;
|
|
}
|
|
|
|
return Ok;
|
|
}// GetFrameDimensionsCount()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get an ID list of dimensions the image supports
|
|
*
|
|
* Arguments:
|
|
*
|
|
* dimensionIDs---Memory buffer to hold the result ID list
|
|
* count -- number of dimensions this image format supports
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 03/20/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetFrameDimensionsList(
|
|
GUID* dimensionIDs,
|
|
UINT count
|
|
) const
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if ( dimensionIDs == NULL )
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if ( Img == NULL )
|
|
{
|
|
// This CopyOnWriteBitmap is not created from a source image. It doesn't
|
|
// have source Stream, nor source file name. It might be created
|
|
// from a BITMAPINFO structure or a memory bitmap. But anyway, it has
|
|
// one PAGE frame. So we set the return values accordingly.
|
|
// Note: in this case, the "count" has to be 1
|
|
|
|
if ( count == 1 )
|
|
{
|
|
dimensionIDs[0] = FRAMEDIM_PAGE;
|
|
|
|
return Ok;
|
|
}
|
|
else
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
|
|
// Ask the lower level codec to give us the answer
|
|
|
|
HRESULT hResult = Img->GetFrameDimensionsList(dimensionIDs, count);
|
|
|
|
if ( hResult == E_NOTIMPL )
|
|
{
|
|
return NotImplemented;
|
|
}
|
|
else if ( hResult != S_OK )
|
|
{
|
|
return Win32Error;
|
|
}
|
|
|
|
return Ok;
|
|
}// GetFrameDimensionsList()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Select active frame in a bitmap image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* dimensionID - dimension GUID used to specify which dimention you want to
|
|
* set the active frame, PAGE, TIMER, RESOLUTION
|
|
* frameIndex -- Index number of the frame you want to set
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 11/19/1999 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SelectActiveFrame(
|
|
const GUID* dimensionID,
|
|
UINT frameIndex
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if ( frameIndex == CurrentFrameIndex )
|
|
{
|
|
// We are already at the required frame. Do nothing
|
|
|
|
return Ok;
|
|
}
|
|
|
|
// Cannot move onto another frame if the current bits is locked
|
|
|
|
if ( ObjRefCount > 1 )
|
|
{
|
|
return WrongState;
|
|
}
|
|
|
|
// Set active frame to caller asks for
|
|
// Note: we don't need to validate the "frameIndex" range since the lower
|
|
// level will return fail if the page number if not correct. By doing this
|
|
// way, we avoid remembering the total number of frames in an image
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img == NULL )
|
|
{
|
|
// Try to create a GpDecodedImage*
|
|
|
|
if ( NULL != Stream )
|
|
{
|
|
hResult = GpDecodedImage::CreateFromStream(Stream, &Img);
|
|
}
|
|
else if ( NULL != Filename )
|
|
{
|
|
hResult = GpDecodedImage::CreateFromFile(Filename, &Img);
|
|
}
|
|
else
|
|
{
|
|
// This CopyOnWriteBitmap is not created from a source image. It
|
|
// might be created from a BITMAPINFO structure or a memory bitmap.
|
|
// But anyway, the caller is allowed to call this function though it
|
|
// is just a NO-OP.
|
|
|
|
return Ok;
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SelectActiveFrame-Create Img failed"));
|
|
return Win32Error;
|
|
}
|
|
}
|
|
|
|
hResult = Img->SelectActiveFrame(dimensionID, frameIndex);
|
|
|
|
if ( hResult == E_NOTIMPL )
|
|
{
|
|
return NotImplemented;
|
|
}
|
|
else if ( hResult != S_OK )
|
|
{
|
|
WARNING(("Bitmap::SelectActiveFrame--Img->SelectActiveFrame() failed"));
|
|
return Win32Error;
|
|
}
|
|
|
|
// Get the image info of the new frame
|
|
// Note: we can't overwrite our "SrcImageInfo" for now until all the OPs
|
|
// are successful
|
|
|
|
ImageInfo tempImageInfo;
|
|
hResult = Img->GetImageInfo(&tempImageInfo);
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return Win32Error;
|
|
}
|
|
|
|
// Create a temporary memory bitmap for the active frame.
|
|
// Note: we can't release Bmp first and stick &Bmp in the call because
|
|
// this call might fail. We don't want to lose the original if this happens
|
|
|
|
GpMemoryBitmap* newbmp;
|
|
|
|
hResult = GpMemoryBitmap::CreateFromImage(Img,
|
|
0,
|
|
0,
|
|
tempImageInfo.PixelFormat,
|
|
InterpolationHintDefault,
|
|
&newbmp,
|
|
NULL,
|
|
NULL);
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
return Win32Error;
|
|
}
|
|
|
|
// We can release the old one if there is one since we got the new frame
|
|
// successfully
|
|
// Note: it is possible there is no any old one because we haven't load the
|
|
// image into memory yet (Bmp == NULL)
|
|
|
|
if ( Bmp != NULL )
|
|
{
|
|
Bmp->Release();
|
|
}
|
|
|
|
Bmp = newbmp;
|
|
State = MemBitmap;
|
|
|
|
// Remember the image info of the source image and the pixel format in the
|
|
// memory. They are the same at this moment
|
|
|
|
GpMemcpy(&SrcImageInfo, &tempImageInfo, sizeof(ImageInfo));
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
// Remember current frame index number
|
|
|
|
CurrentFrameIndex = frameIndex;
|
|
|
|
return Ok;
|
|
}// SelectActiveFrame()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the count of property items in the image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [OUT]numOfProperty - The number of property items in the image
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPropertyCount(
|
|
UINT* numOfProperty
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetPropertyCount(numOfProperty);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
|
|
hResult = Bmp->GetPropertyCount(numOfProperty);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetPropertyCount()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get a list of property IDs for all the property items in the image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] numOfProperty - The number of property items in the image
|
|
* [OUT] list----------- A memory buffer the caller provided for storing the
|
|
* ID list
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPropertyIdList(
|
|
IN UINT numOfProperty,
|
|
IN OUT PROPID* list
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetPropertyIdList(numOfProperty, list);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
hResult = Bmp->GetPropertyIdList(numOfProperty, list);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetPropertyIdList()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the size, in bytes, of a specific property item, specified by the
|
|
* property ID
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]propId - The ID of a property item caller is interested
|
|
* [OUT]size--- Size of this property, in bytes
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPropertyItemSize(
|
|
IN PROPID propId,
|
|
OUT UINT* size
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetPropertyItemSize(propId, size);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
|
|
hResult = Bmp->GetPropertyItemSize(propId, size);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetPropertyItemSize()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get a specific property item, specified by the prop ID.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]propId -- The ID of the property item caller is interested
|
|
* [IN]propSize- Size of the property item. The caller has allocated these
|
|
* "bytes of memory" for storing the result
|
|
* [OUT]pBuffer- A memory buffer for storing this property item
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPropertyItem(
|
|
IN PROPID propId,
|
|
IN UINT propSize,
|
|
IN OUT PropertyItem* pBuffer
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetPropertyItem(propId, propSize, pBuffer);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
hResult = Bmp->GetPropertyItem(propId, propSize, pBuffer);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetPropertyItem()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the size of ALL property items in the image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [OUT]totalBufferSize-- Total buffer size needed, in bytes, for storing all
|
|
* property items in the image
|
|
* [OUT]numOfProperty --- The number of property items in the image
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPropertySize(
|
|
OUT UINT* totalBufferSize,
|
|
OUT UINT* numProperties
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetPropertySize(totalBufferSize, numProperties);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
hResult = Bmp->GetPropertySize(totalBufferSize, numProperties);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetPropertySize()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get ALL property items in the image
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]totalBufferSize-- Total buffer size, in bytes, the caller has allocated
|
|
* memory for storing all property items in the image
|
|
* [IN]numOfProperty --- The number of property items in the image
|
|
* [OUT]allItems-------- A memory buffer caller has allocated for storing all
|
|
* the property items
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetAllPropertyItems(
|
|
IN UINT totalBufferSize,
|
|
IN UINT numProperties,
|
|
IN OUT PropertyItem* allItems
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->GetAllPropertyItems(totalBufferSize, numProperties,
|
|
allItems);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
hResult = Bmp->GetAllPropertyItems(totalBufferSize, numProperties,
|
|
allItems);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// GetAllPropertyItems()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Remove a specific property item, specified by the prop ID.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]propId -- The ID of the property item to be removed
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::RemovePropertyItem(
|
|
IN PROPID propId
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hResult = Img->RemovePropertyItem(propId);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
hResult = Bmp->RemovePropertyItem(propId);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// RemovePropertyItem()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set a property item, specified by the propertyitem structure. If the item
|
|
* already exists, then its contents will be updated. Otherwise a new item
|
|
* will be added
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]item -- A property item the caller wants to set
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 02/28/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetPropertyItem(
|
|
IN PropertyItem* item
|
|
)
|
|
{
|
|
if ( item == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::SetPropertyItem-Invalid input parameter"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
ASSERT(IsValid());
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
// Check if we have a source image. Img is NULL means this CopyOnWriteBitmap
|
|
// is not created from a source image.It might be created from a BITMAPINFO
|
|
// structure or a memory bitmap.
|
|
// If "SpecialJPEGSave" is TRUE, it means, the image has been rotated in
|
|
// memory, but the "Img" pointer might not be released yet
|
|
|
|
if (( Img != NULL ) && (SpecialJPEGSave == FALSE))
|
|
{
|
|
hResult = Img->SetPropertyItem(*item);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
|
|
hResult = Bmp->SetPropertyItem(*item);
|
|
}
|
|
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}// SetPropertyItem()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get bitmap image thumbnail
|
|
*
|
|
* Arguments:
|
|
*
|
|
* thumbWidth, thumbHeight - Desired width and height of thumbnail
|
|
* Both zero means pick a default size
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the new thumbnail image object
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
CopyOnWriteBitmap *
|
|
CopyOnWriteBitmap::GetThumbnail(
|
|
UINT thumbWidth,
|
|
UINT thumbHeight,
|
|
GetThumbnailImageAbort callback,
|
|
VOID *callbackData
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
HRESULT hr = S_OK;
|
|
IImage *newImage = NULL;
|
|
|
|
// Ask the lower level codec to give us the thumbnail stored in the image.
|
|
// Note: If there is no thumbnail stored in the original image, this
|
|
// function will return us a scaled version of the original image as the
|
|
// thumbnail image, at DEFAULT_THUMBNAIL_SIZE
|
|
// Note: Img might be zero, it means this CopyOnWriteBitmap is not created from an
|
|
// stream or file. It might be created from an memory buffer or something
|
|
// else. One scenario will be Do a GetThumbnail() and then do another
|
|
// GetThumbnail from this thumbnail. Though it is a weird scenario. But it
|
|
// could happen. So if the Img is NULL, then we create a thumbnail from
|
|
// the memory bitmap
|
|
|
|
if ( Img != NULL )
|
|
{
|
|
hr = Img->GetThumbnail(thumbWidth, thumbHeight, &newImage);
|
|
}
|
|
else
|
|
{
|
|
GpStatus status = LoadIntoMemory();
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
hr = Bmp->GetThumbnail(thumbWidth, thumbHeight, &newImage);
|
|
}
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Create a GpMemoryBitmap from IImage
|
|
|
|
ImageInfo srcImageInfo;
|
|
newImage->GetImageInfo(&srcImageInfo);
|
|
|
|
GpMemoryBitmap* pMemBitmap;
|
|
|
|
hr = GpMemoryBitmap::CreateFromImage(newImage,
|
|
srcImageInfo.Width,
|
|
srcImageInfo.Height,
|
|
srcImageInfo.PixelFormat,
|
|
InterpolationHintDefault,
|
|
&pMemBitmap,
|
|
(DrawImageAbort) callback,
|
|
callbackData
|
|
);
|
|
|
|
// Release the COM obj IImage
|
|
|
|
newImage->Release();
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
CopyOnWriteBitmap* thumbBitmap = new CopyOnWriteBitmap(pMemBitmap);
|
|
|
|
return thumbBitmap;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Access bitmap pixel data
|
|
*
|
|
* Arguments:
|
|
*
|
|
* rect - Specifies the interested image area
|
|
* NULL means the entire image
|
|
* flags - Desired access mode
|
|
* format - Desired pixel format
|
|
* bmpdata - Returns information about bitmap pixel data
|
|
* width,
|
|
* height - suggested width and height to decode to.
|
|
* zero is the source image width and height.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::LockBits(
|
|
const GpRect* rect,
|
|
UINT flags,
|
|
PixelFormatID format,
|
|
BitmapData* bmpdata,
|
|
INT width,
|
|
INT height
|
|
) const
|
|
{
|
|
ASSERT(IsValid());
|
|
ASSERT(width>=0);
|
|
ASSERT(height>=0);
|
|
|
|
// LockBits cannot be nested
|
|
|
|
if ( ObjRefCount > 1 )
|
|
{
|
|
return WrongState;
|
|
}
|
|
|
|
// Do some sanity check to see if we can do it or not
|
|
|
|
if ( (format == PIXFMT_DONTCARE)
|
|
||(!IsValidPixelFormat(format)) )
|
|
{
|
|
// Wrong pixel format
|
|
|
|
WARNING(("CopyOnWriteBitmap::LockBits---invalid format"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// Valid format. If the lock is for READ, we need to check if we can
|
|
// convert the current source to this format
|
|
|
|
EpFormatConverter linecvt;
|
|
|
|
if ( flags & ImageLockModeRead )
|
|
{
|
|
if ( IsDirty() == FALSE )
|
|
{
|
|
if ( linecvt.CanDoConvert(SrcImageInfo.PixelFormat, format)==FALSE )
|
|
{
|
|
WARNING(("LockBits--can't convert src to specified fmt"));
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
else if (linecvt.CanDoConvert(PixelFormatInMem, format) == FALSE )
|
|
{
|
|
WARNING(("LockBits--can't convert src to specified fmt"));
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
|
|
// If the lock is for WRITE, we need to check if we can convert the format
|
|
// back to current source format. The reason we need to do this checking is
|
|
// when the user calls UnLockBits() after he has modified the locked area,
|
|
// we need to convert this small area back to the format the whole image is
|
|
// at. E.x. For an 4 bpp image, the caller can lock a small area at 32 bpp
|
|
// (this makes the app code easier), do some pixel modification on that
|
|
// area, unlock it. We need to convert that small area back to 4 bpp.
|
|
|
|
if ( flags & ImageLockModeWrite )
|
|
{
|
|
if ( IsDirty() == FALSE )
|
|
{
|
|
if ( linecvt.CanDoConvert(format, SrcImageInfo.PixelFormat)==FALSE )
|
|
{
|
|
WARNING(("LockBits--can't convert specified fmt back to src"));
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
else if (linecvt.CanDoConvert(format, PixelFormatInMem) == FALSE )
|
|
{
|
|
WARNING(("LockBits--can't convert specified format back to src"));
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
|
|
HRESULT hr;
|
|
|
|
if ( (IsDirty() == FALSE)
|
|
&&(State >= MemBitmap)
|
|
&&(format != PixelFormatInMem)
|
|
&&(SrcImageInfo.PixelFormat != PixelFormatInMem)
|
|
&&(Img != NULL) )
|
|
{
|
|
// If the image is:
|
|
// 1) Not dirty
|
|
// 2) Was loaded into memory with different color depth for some reason,
|
|
// like DrawImage()
|
|
// 3) The color depth the caller wants to locked for is different than
|
|
// the one in memory
|
|
// 4) We have an source image
|
|
//
|
|
// Then we can throw away the bits in memory and reload the bits from
|
|
// the original with the color depth user asks for. The purpose of this
|
|
// is to increase the success rate for LockBits(). One of the problem
|
|
// we are having now is that our DrawImage() always load image into
|
|
// memory at 32PARGB format. This makes tasks like printing very
|
|
// expensive because it has to send 32PARGB format to the print. We'd
|
|
// like to send the original color depth to the printer
|
|
//
|
|
// Note: this "throw away" approach won't hurt our DrawImage() work flow
|
|
// Here is the reason why: say we have a 4 bpp source image. We do a
|
|
// DrawImage() first, thus we load it into memory at 32 PARGB. When the
|
|
// printing request coming. It prefers to send down 4bpp to the printer.
|
|
// So we through away the 32 PARGB in memory and reload the image in
|
|
// 4 bpp mode and send it to printer. Later on, if DrawImage() is called
|
|
// again. It can still pass the above "if" checking condition and reload
|
|
// the image in as 32 PARGB.
|
|
|
|
ASSERT( Bmp != NULL )
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
State = DecodedImg;
|
|
PixelFormatInMem = PixelFormatUndefined;
|
|
}
|
|
|
|
// Load the image into memory using the suggested width and height.
|
|
// if the suggested width and height are zero, use the source
|
|
// image width and height.
|
|
// Load the image into memory before querying the pixel format because
|
|
// the load could potentially change the in-memory format.
|
|
|
|
GpStatus status = LoadIntoMemory(format, NULL, NULL, width, height);
|
|
|
|
if (status != Ok)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::LockBits()----LoadIntoMemory() failed"));
|
|
return status;
|
|
}
|
|
|
|
if ( rect == NULL )
|
|
{
|
|
hr = Bmp->LockBits(NULL, flags, format, bmpdata);
|
|
}
|
|
else
|
|
{
|
|
RECT r =
|
|
{
|
|
rect->X,
|
|
rect->Y,
|
|
rect->GetRight(),
|
|
rect->GetBottom()
|
|
};
|
|
|
|
hr = Bmp->LockBits(&r, flags, format, bmpdata);
|
|
}
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::LockBits()----LockBits() failed"));
|
|
return (MapHRESULTToGpStatus(hr));
|
|
}
|
|
|
|
ObjRefCount++;
|
|
|
|
if ( flags & ImageLockModeWrite )
|
|
{
|
|
// Mark the bits dirty since the user might have changed the bits during
|
|
// the lock period
|
|
|
|
SetDirtyFlag(TRUE);
|
|
}
|
|
|
|
return Ok;
|
|
}// LockBits()
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::UnlockBits(
|
|
BitmapData* bmpdata,
|
|
BOOL Destroy
|
|
) const
|
|
{
|
|
ASSERT(ObjRefCount == 2);
|
|
|
|
if ( NULL == Bmp )
|
|
{
|
|
// The caller should not call UnlockBits() if it hasn't called
|
|
// LockBits() yet
|
|
|
|
WARNING(("UnlockBits---Call UnlockBits() without calling LockBits()"));
|
|
return GenericError;
|
|
}
|
|
|
|
HRESULT hr = Bmp->UnlockBits(bmpdata);
|
|
ObjRefCount--;
|
|
|
|
// Called to destroy the decoded bits because we decoded a partial image
|
|
// and it won't be valid on the next call.
|
|
|
|
if(Destroy)
|
|
{
|
|
// Revert the state back to DecodedImg (which means not decoded).
|
|
|
|
ASSERT(Img != NULL);
|
|
delete Bmp;
|
|
Bmp = NULL;
|
|
State = DecodedImg;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
WARNING(("GpBitmap::UnlockBits---Bmp->UnlockBits() failed"));
|
|
return (MapHRESULTToGpStatus(hr));
|
|
}
|
|
|
|
return Ok;
|
|
}// UnlockBits()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get a pixel
|
|
*
|
|
* Arguments:
|
|
*
|
|
* IN x, y: Coordinates of the pixel to get.
|
|
* OUT color: color value of the specified pixel.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPixel(INT x, INT y, ARGB *color)
|
|
{
|
|
// Get the bitmap info.
|
|
BitmapData bmpData;
|
|
|
|
// Only lock the required rectangle.
|
|
GpRect pixelRect(x, y, 1, 1);
|
|
|
|
GpStatus status = LockBits(
|
|
&pixelRect,
|
|
IMGLOCK_READ,
|
|
PIXFMT_32BPP_ARGB,
|
|
&bmpData
|
|
);
|
|
|
|
// Failed to lock the bits.
|
|
if(status != Ok)
|
|
{
|
|
return(status);
|
|
}
|
|
|
|
ARGB *pixel = static_cast<ARGB *>(bmpData.Scan0);
|
|
*color = *pixel;
|
|
|
|
return UnlockBits(&bmpData);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set a pixel
|
|
*
|
|
* Arguments:
|
|
*
|
|
* IN x, y: Coordinates of the pixel to set.
|
|
* IN color: color value for the specified pixel.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetPixel(INT x, INT y, ARGB color)
|
|
{
|
|
// Get the bitmap info.
|
|
BitmapData bmpData;
|
|
|
|
// Only lock the required rectangle.
|
|
GpRect pixelRect(x, y, 1, 1);
|
|
|
|
GpStatus status = LockBits(
|
|
&pixelRect,
|
|
IMGLOCK_WRITE,
|
|
PIXFMT_32BPP_ARGB,
|
|
&bmpData
|
|
);
|
|
|
|
// Failed to lock the bits.
|
|
if(status != Ok)
|
|
{
|
|
return(status);
|
|
}
|
|
|
|
ARGB* pixel = static_cast<ARGB *>(bmpData.Scan0);
|
|
*pixel = color;
|
|
|
|
return UnlockBits(&bmpData);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Convert bitmap image to a different pixel format
|
|
*
|
|
* Arguments:
|
|
*
|
|
* format - Specifies the new pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ConvertFormat(
|
|
PixelFormatID format,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData
|
|
)
|
|
{
|
|
ASSERT(ObjRefCount == 1);
|
|
|
|
// If bitmap not in memory yet, simply force load using specified format:
|
|
|
|
if ( State < MemBitmap )
|
|
{
|
|
return LoadIntoMemory(format, callback, callbackData);
|
|
}
|
|
|
|
HRESULT hr;
|
|
|
|
if ( PixelFormatInMem != format)
|
|
{
|
|
GpMemoryBitmap* newbmp;
|
|
|
|
hr = GpMemoryBitmap::CreateFromImage(
|
|
Bmp,
|
|
0,
|
|
0,
|
|
format,
|
|
InterpolationHintDefault,
|
|
&newbmp,
|
|
callback,
|
|
callbackData);
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::ConvertFormat---CreateFromImage() failed"));
|
|
return OutOfMemory;
|
|
}
|
|
|
|
Bmp->Release();
|
|
Bmp = newbmp;
|
|
|
|
PixelFormatInMem = format;
|
|
|
|
// We change the source pixel format info as well because this image
|
|
// is dirty now and we should not convert it back to original format
|
|
|
|
SrcImageInfo.PixelFormat = format;
|
|
|
|
// Mark the bits dirty since the original image bits got changed
|
|
|
|
// !!! TODO: we can't set it dirty for now because DrawImage() always
|
|
// convert an image to 32 bpp first. When this temporary solution is removed
|
|
// we should reset this flag
|
|
//
|
|
// SetDirtyFlag(TRUE);
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Derive a graphics context on top of the bitmap object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the derived graphics context
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
/******************************Public*Routine******************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Derive an HDC on top of the bitmap object for GDI interop
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* HDC with a bitmap selected into it that is associated with this GDI+
|
|
* bitmap.
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
HDC
|
|
CopyOnWriteBitmap::GetHdc()
|
|
{
|
|
HDC hdc = NULL;
|
|
HBITMAP hbm = NULL;
|
|
|
|
// Create the HDC and HBITMAP if needed.
|
|
|
|
if (InteropData.Hdc == NULL)
|
|
{
|
|
ImageInfo imageInfo;
|
|
|
|
CopyOnWriteBitmap::GetImageInfo(&imageInfo);
|
|
|
|
hdc = CreateCompatibleDC(NULL);
|
|
|
|
if (hdc == NULL)
|
|
{
|
|
goto cleanup_exit;
|
|
}
|
|
|
|
BITMAPINFO gdiBitmapInfo;
|
|
|
|
gdiBitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
gdiBitmapInfo.bmiHeader.biWidth = imageInfo.Width;
|
|
gdiBitmapInfo.bmiHeader.biHeight = - static_cast<LONG>
|
|
(imageInfo.Height);
|
|
gdiBitmapInfo.bmiHeader.biPlanes = 1;
|
|
gdiBitmapInfo.bmiHeader.biBitCount = 32;
|
|
gdiBitmapInfo.bmiHeader.biCompression = BI_RGB;
|
|
gdiBitmapInfo.bmiHeader.biSizeImage = 0;
|
|
gdiBitmapInfo.bmiHeader.biXPelsPerMeter = 0;
|
|
gdiBitmapInfo.bmiHeader.biYPelsPerMeter = 0;
|
|
gdiBitmapInfo.bmiHeader.biClrUsed = 0;
|
|
gdiBitmapInfo.bmiHeader.biClrImportant = 0;
|
|
|
|
hbm = CreateDIBSection(hdc,
|
|
&gdiBitmapInfo,
|
|
DIB_RGB_COLORS,
|
|
&InteropData.Bits,
|
|
NULL,
|
|
0);
|
|
|
|
DIBSECTION gdiDibInfo;
|
|
|
|
if ((hbm == NULL) ||
|
|
(GetObjectA(hbm, sizeof(gdiDibInfo), &gdiDibInfo) == 0) ||
|
|
(gdiDibInfo.dsBmih.biSize == 0) ||
|
|
(SelectObject(hdc, hbm) == NULL))
|
|
{
|
|
goto cleanup_exit;
|
|
}
|
|
|
|
InteropData.Hdc = hdc;
|
|
InteropData.Hbm = hbm;
|
|
|
|
InteropData.Width = imageInfo.Width;
|
|
InteropData.Height = imageInfo.Height;
|
|
InteropData.Stride = gdiDibInfo.dsBm.bmWidthBytes;
|
|
|
|
// Since it's a 32bpp bitmap, we can assume that a tightly packed
|
|
// bitmap is already satisfies the scanline align constraints
|
|
// (letting us fill the bitmap with a very simple loop).
|
|
ASSERT(gdiDibInfo.dsBm.bmWidthBytes == static_cast<LONG>(imageInfo.Width * 4));
|
|
}
|
|
|
|
ASSERT(InteropData.Hdc != NULL);
|
|
|
|
// Fill the bitmap with a sentinal pattern.
|
|
|
|
{
|
|
INT count = InteropData.Width * InteropData.Height;
|
|
UINT32 *bits = static_cast<UINT32*>(InteropData.Bits);
|
|
|
|
while (count--)
|
|
{
|
|
*bits++ = GDIP_TRANSPARENT_COLOR_KEY;
|
|
}
|
|
}
|
|
|
|
return InteropData.Hdc;
|
|
|
|
cleanup_exit:
|
|
|
|
if (hdc)
|
|
DeleteDC(hdc);
|
|
|
|
if (hbm)
|
|
DeleteObject(hbm);
|
|
|
|
return reinterpret_cast<HDC>(NULL);
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Release the HDC returned by CopyOnWriteBitmap::GetHdc. If necessary, updates
|
|
* the GDI+ bitmap with the GDI drawing (may not be necessary if the
|
|
* GDI and GDI+ bitmaps share a common underlying pixel buffer).
|
|
*
|
|
* Arguments:
|
|
*
|
|
* HDC to release
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to the derived graphics context
|
|
* NULL if there is an error
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID
|
|
CopyOnWriteBitmap::ReleaseHdc(HDC hdc)
|
|
{
|
|
ASSERT(hdc == InteropData.Hdc);
|
|
|
|
GdiFlush();
|
|
|
|
// Scan the GDI bitmap to see if any of the sentinal pixels have changed.
|
|
// If any are detected, copy it to the GDI+ bitmap with opaque alpha set.
|
|
|
|
int curRow, curCol;
|
|
BYTE *interopScan = static_cast<BYTE*>(InteropData.Bits);
|
|
|
|
GpStatus status = Ok;
|
|
|
|
for (curRow = 0; (curRow < InteropData.Height) && (status == Ok); curRow++)
|
|
{
|
|
BOOL rowLocked = FALSE;
|
|
BitmapData bitmapData;
|
|
|
|
ARGB *interopPixel = static_cast<ARGB*>(static_cast<VOID*>(interopScan));
|
|
ARGB *pixel = NULL;
|
|
|
|
for (curCol = 0; curCol < InteropData.Width; curCol++)
|
|
{
|
|
if ((*interopPixel & 0x00ffffff) != GDIP_TRANSPARENT_COLOR_KEY)
|
|
{
|
|
if (!rowLocked)
|
|
{
|
|
GpRect lockRect(0, curRow, InteropData.Width, 1);
|
|
|
|
status = LockBits(&lockRect,
|
|
IMGLOCK_READ | IMGLOCK_WRITE,
|
|
PIXFMT_32BPP_ARGB,
|
|
&bitmapData);
|
|
|
|
if (status == Ok)
|
|
{
|
|
pixel = static_cast<ARGB*>(bitmapData.Scan0) + curCol;
|
|
rowLocked = TRUE;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
*pixel = *interopPixel | 0xFF000000;
|
|
}
|
|
|
|
interopPixel++;
|
|
pixel++;
|
|
}
|
|
|
|
if (rowLocked)
|
|
UnlockBits(&bitmapData);
|
|
|
|
interopScan += InteropData.Stride;
|
|
}
|
|
}
|
|
|
|
// Data flags
|
|
#define COMPRESSED_IMAGE 0x00000001
|
|
|
|
class BitmapRecordData : public ObjectTypeData
|
|
{
|
|
public:
|
|
INT32 Width;
|
|
INT32 Height;
|
|
INT32 Stride;
|
|
INT32 PixelFormat;
|
|
INT32 Flags;
|
|
};
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the bitmap data.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] dataBuffer - fill this buffer with the data
|
|
* [IN/OUT] size - IN - size of buffer; OUT - number bytes written
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok or error code
|
|
*
|
|
* Created:
|
|
*
|
|
* 9/13/1999 DCurtis
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetData(
|
|
IStream * stream
|
|
) const
|
|
{
|
|
ASSERT(stream);
|
|
|
|
GpStatus status;
|
|
BitmapRecordData bitmapRecordData;
|
|
|
|
IStream* imageStream = NULL;
|
|
STATSTG statStg;
|
|
BOOL needRelease = FALSE;
|
|
|
|
// variables used to track the stream state
|
|
|
|
LARGE_INTEGER zero = {0,0};
|
|
LARGE_INTEGER oldPos;
|
|
BOOL isSeekableStream = FALSE;
|
|
|
|
// try to get a imageStream
|
|
|
|
if (!IsDirty())
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (Stream != NULL)
|
|
{
|
|
hr = Stream->Seek(zero, STREAM_SEEK_CUR, (ULARGE_INTEGER *)&oldPos);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = Stream->Seek(zero, STREAM_SEEK_SET, NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
isSeekableStream = TRUE;
|
|
imageStream = Stream;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we don't have a CopyOnWriteBitmap::Stream but we have a filename
|
|
|
|
if ((imageStream == NULL) && (Filename != NULL))
|
|
{
|
|
hr = CreateStreamOnFileForRead(Filename, &imageStream);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
needRelease = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// try to write the imageStream out to the metafile Stream
|
|
if (imageStream && imageStream->Stat(&statStg, STATFLAG_NONAME) == S_OK)
|
|
{
|
|
bitmapRecordData.Type = ImageTypeBitmap;
|
|
bitmapRecordData.Width = 0;
|
|
bitmapRecordData.Height = 0;
|
|
bitmapRecordData.Stride = 0;
|
|
bitmapRecordData.PixelFormat = 0;
|
|
bitmapRecordData.Flags = COMPRESSED_IMAGE;
|
|
stream->Write(&bitmapRecordData, sizeof(bitmapRecordData), NULL);
|
|
|
|
// Read data from the imageStream into the dest stream
|
|
// Unfortunately, we can't assume that CopyTo has been implemented.
|
|
// Is there some way to find out? !!!
|
|
#define COPY_BUFFER_SIZE 2048
|
|
|
|
BYTE buffer[COPY_BUFFER_SIZE];
|
|
UINT streamSize = statStg.cbSize.LowPart;
|
|
UINT sizeToRead = COPY_BUFFER_SIZE;
|
|
UINT numPadBytes = 0;
|
|
|
|
if ((streamSize & 0x03) != 0)
|
|
{
|
|
numPadBytes = 4 - (streamSize & 0x03);
|
|
}
|
|
|
|
status = Ok;
|
|
|
|
if (status == Ok)
|
|
{
|
|
HRESULT hr;
|
|
ULONG bytesRead = 0;
|
|
ULONG bytesWrite = 0;
|
|
|
|
while (streamSize > 0)
|
|
{
|
|
if (sizeToRead > streamSize)
|
|
{
|
|
sizeToRead = streamSize;
|
|
}
|
|
|
|
hr = imageStream->Read(buffer, sizeToRead, &bytesRead);
|
|
|
|
if (!SUCCEEDED(hr) || (sizeToRead != bytesRead))
|
|
{
|
|
WARNING(("Failed to read stream in CopyOnWriteBitmap::GetData"));
|
|
status = Win32Error;
|
|
break;
|
|
}
|
|
|
|
hr = stream->Write(buffer, sizeToRead, &bytesWrite);
|
|
|
|
if (!SUCCEEDED(hr) || (sizeToRead != bytesWrite))
|
|
{
|
|
WARNING(("Failed to write stream in CopyOnWriteBitmap::GetData"));
|
|
status = Win32Error;
|
|
break;
|
|
}
|
|
|
|
streamSize -= sizeToRead;
|
|
}
|
|
|
|
// align
|
|
if (numPadBytes > 0)
|
|
{
|
|
INT pad = 0;
|
|
stream->Write(&pad, numPadBytes, NULL);
|
|
}
|
|
}
|
|
|
|
if (isSeekableStream)
|
|
{
|
|
// move back to the old pos
|
|
Stream->Seek(oldPos, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
if (needRelease)
|
|
{
|
|
imageStream->Release();
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// we can't record compressed data, record the uncompressed data
|
|
|
|
if ((status = (const_cast<CopyOnWriteBitmap *>(this))->LoadIntoMemory()) != Ok)
|
|
{
|
|
WARNING(("Couldn't load the image into memory"));
|
|
return status;
|
|
}
|
|
|
|
BitmapData bitmapData = *Bmp;
|
|
INT positiveStride = bitmapData.Stride;
|
|
BOOL upsideDown = FALSE;
|
|
INT paletteSize = 0;
|
|
INT pixelDataSize;
|
|
|
|
if (positiveStride < 0)
|
|
{
|
|
positiveStride = -positiveStride;
|
|
upsideDown = TRUE;
|
|
}
|
|
|
|
pixelDataSize = (bitmapData.Height * positiveStride);
|
|
|
|
if (IsIndexedPixelFormat(bitmapData.PixelFormat))
|
|
{
|
|
// We're an indexed pixel format - must have a valid palette.
|
|
ASSERT(Bmp->colorpal != NULL);
|
|
|
|
// Note sizeof(ColorPalette) includes the first palette entry.
|
|
paletteSize = sizeof(ColorPalette) +
|
|
sizeof(ARGB)*(Bmp->colorpal->Count-1);
|
|
}
|
|
|
|
bitmapRecordData.Type = ImageTypeBitmap;
|
|
bitmapRecordData.Width = bitmapData.Width;
|
|
bitmapRecordData.Height = bitmapData.Height;
|
|
bitmapRecordData.Stride = positiveStride;
|
|
bitmapRecordData.PixelFormat = bitmapData.PixelFormat;
|
|
bitmapRecordData.Flags = 0;
|
|
stream->Write(&bitmapRecordData, sizeof(bitmapRecordData), NULL);
|
|
|
|
if (paletteSize > 0)
|
|
{
|
|
// Write out the palette
|
|
stream->Write(Bmp->colorpal, paletteSize, NULL);
|
|
}
|
|
|
|
if (pixelDataSize > 0)
|
|
{
|
|
if (!upsideDown)
|
|
{
|
|
stream->Write(bitmapData.Scan0, pixelDataSize, NULL);
|
|
}
|
|
else
|
|
{
|
|
BYTE * scan = (BYTE *)bitmapData.Scan0;
|
|
for (INT i = bitmapData.Height; i > 0; i--)
|
|
{
|
|
stream->Write(scan, positiveStride, NULL);
|
|
scan -= positiveStride;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the compressed image data.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* OUT compressed_data
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok or error code
|
|
*
|
|
* Created:
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetCompressedData(
|
|
DpCompressedData * compressed_data,
|
|
BOOL getJPEG,
|
|
BOOL getPNG,
|
|
HDC hdc
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
IStream* imageStream = NULL;
|
|
STATSTG statStg;
|
|
BOOL needRelease = FALSE;
|
|
|
|
// variables used to track the stream state
|
|
|
|
LARGE_INTEGER zero = {0,0};
|
|
LARGE_INTEGER oldPos;
|
|
BOOL isSeekableStream = FALSE;
|
|
|
|
ASSERT(compressed_data->buffer == NULL);
|
|
|
|
if (Img)
|
|
{
|
|
if (SrcImageInfo.RawDataFormat == IMGFMT_JPEG)
|
|
{
|
|
if (!getJPEG)
|
|
{
|
|
return Ok;
|
|
}
|
|
compressed_data->format = BI_JPEG;
|
|
}
|
|
else if (SrcImageInfo.RawDataFormat == IMGFMT_PNG)
|
|
{
|
|
if (!getPNG)
|
|
{
|
|
return Ok;
|
|
}
|
|
compressed_data->format = BI_PNG;
|
|
}
|
|
else
|
|
return Ok;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("GetCompressedData: Decoded image not available."));
|
|
return Ok;
|
|
}
|
|
// try to get a imageStream
|
|
|
|
if (!IsDirty())
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (Stream != NULL)
|
|
{
|
|
hr = Stream->Seek(zero, STREAM_SEEK_CUR, (ULARGE_INTEGER *)&oldPos);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = Stream->Seek(zero, STREAM_SEEK_SET, NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
isSeekableStream = TRUE;
|
|
imageStream = Stream;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we don't have a CopyOnWriteBitmap::Stream but we have a filename
|
|
|
|
if ((imageStream == NULL) && (Filename != NULL))
|
|
{
|
|
hr = CreateStreamOnFileForRead(Filename, &imageStream);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
needRelease = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (imageStream && (imageStream->Stat(&statStg, STATFLAG_NONAME) == S_OK))
|
|
{
|
|
UINT streamSize = statStg.cbSize.LowPart;
|
|
ULONG bytesRead = 0;
|
|
|
|
VOID * buffer = (PVOID)GpMalloc(streamSize);
|
|
|
|
if (buffer)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = imageStream->Read(buffer, streamSize, &bytesRead);
|
|
|
|
if (!SUCCEEDED(hr) || (streamSize != bytesRead))
|
|
{
|
|
WARNING(("Failed to read stream in CopyOnWriteBitmap::GetData"));
|
|
status = Win32Error;
|
|
}
|
|
else
|
|
{
|
|
compressed_data->bufferSize = streamSize;
|
|
compressed_data->buffer = buffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("Out of memory"));
|
|
status = OutOfMemory;
|
|
}
|
|
}
|
|
|
|
if (isSeekableStream)
|
|
{
|
|
// move back to the old pos
|
|
Stream->Seek(oldPos, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
if (needRelease)
|
|
{
|
|
imageStream->Release();
|
|
}
|
|
|
|
if ((hdc != NULL) && (compressed_data->buffer != NULL))
|
|
{
|
|
DWORD EscapeValue = (compressed_data->format == BI_JPEG) ?
|
|
CHECKJPEGFORMAT : CHECKPNGFORMAT;
|
|
|
|
DWORD result = 0;
|
|
|
|
// Call escape to determine if this particular image is supported
|
|
if ((ExtEscape(hdc,
|
|
EscapeValue,
|
|
compressed_data->bufferSize,
|
|
(LPCSTR)compressed_data->buffer,
|
|
sizeof(DWORD),
|
|
(LPSTR)&result) <= 0) || (result != 1))
|
|
{
|
|
// Failed to support passthrough of this image, delete the
|
|
// compressed bits.
|
|
|
|
DeleteCompressedData(compressed_data);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::DeleteCompressedData(
|
|
DpCompressedData * compressed_data
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
if (compressed_data && compressed_data->buffer)
|
|
{
|
|
GpFree(compressed_data->buffer);
|
|
compressed_data->buffer = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
UINT
|
|
CopyOnWriteBitmap::GetDataSize() const
|
|
{
|
|
UINT dataSize = 0;
|
|
|
|
// if CopyOnWriteBitmap is not dirty, we look at compressed data
|
|
|
|
if (!IsDirty())
|
|
{
|
|
STATSTG statStg;
|
|
HRESULT hr;
|
|
|
|
if (Stream != NULL)
|
|
{
|
|
// variables used to track the stream state
|
|
LARGE_INTEGER zero = {0,0};
|
|
LARGE_INTEGER oldPos;
|
|
BOOL isSeekableStream = FALSE;
|
|
|
|
hr = Stream->Seek(zero, STREAM_SEEK_CUR, (ULARGE_INTEGER *)&oldPos);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = Stream->Seek(zero, STREAM_SEEK_SET, NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (Stream->Stat(&statStg, STATFLAG_NONAME) == S_OK)
|
|
{
|
|
dataSize = sizeof(BitmapRecordData) + statStg.cbSize.LowPart;
|
|
}
|
|
|
|
// move back to the old pos
|
|
|
|
Stream->Seek(oldPos, STREAM_SEEK_SET, NULL);
|
|
|
|
return ((dataSize + 3) & (~3)); // align
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (Filename != NULL)
|
|
{
|
|
IStream* stream = NULL;
|
|
|
|
hr = CreateStreamOnFileForRead(Filename, &stream);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (stream->Stat(&statStg, STATFLAG_NONAME) == S_OK)
|
|
{
|
|
dataSize = sizeof(BitmapRecordData) + statStg.cbSize.LowPart;
|
|
}
|
|
|
|
stream->Release();
|
|
}
|
|
|
|
return ((dataSize + 3) & (~3)); // align
|
|
}
|
|
}
|
|
|
|
// if we cannot get the compressed data
|
|
if ((const_cast<CopyOnWriteBitmap *>(this))->LoadIntoMemory() == Ok)
|
|
{
|
|
BitmapData bitmapData = *Bmp;
|
|
INT positiveStride = bitmapData.Stride;
|
|
INT paletteSize = 0;
|
|
INT pixelDataSize;
|
|
|
|
if (positiveStride < 0)
|
|
{
|
|
positiveStride = -positiveStride;
|
|
}
|
|
|
|
pixelDataSize = (bitmapData.Height * positiveStride);
|
|
|
|
if (IsIndexedPixelFormat(bitmapData.PixelFormat))
|
|
{
|
|
// We're an indexed pixel format - must have a valid palette.
|
|
ASSERT(Bmp->colorpal != NULL);
|
|
|
|
// Note sizeof(ColorPalette) includes the first palette entry.
|
|
paletteSize = sizeof(ColorPalette) +
|
|
sizeof(ARGB)*(Bmp->colorpal->Count - 1);
|
|
}
|
|
|
|
dataSize = sizeof(BitmapRecordData) + paletteSize + pixelDataSize;
|
|
}
|
|
|
|
return ((dataSize + 3) & (~3)); // align
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Read the bitmap object from memory.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] dataBuffer - the data that was read from the stream
|
|
* [IN] size - the size of the data
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok or failure status
|
|
*
|
|
* Created:
|
|
*
|
|
* 4/26/1999 DCurtis
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetData(
|
|
const BYTE * dataBuffer,
|
|
UINT size
|
|
)
|
|
{
|
|
ASSERT ((GpImageType)(((BitmapRecordData *)dataBuffer)->Type) == ImageTypeBitmap);
|
|
|
|
GpStatus status = Ok;
|
|
|
|
this->FreeData();
|
|
this->InitDefaults();
|
|
|
|
if (dataBuffer == NULL)
|
|
{
|
|
WARNING(("dataBuffer is NULL"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if (size < sizeof(BitmapRecordData))
|
|
{
|
|
WARNING(("size too small"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
const BitmapRecordData * bitmapData;
|
|
|
|
bitmapData = reinterpret_cast<const BitmapRecordData *>(dataBuffer);
|
|
|
|
if (!bitmapData->MajorVersionMatches())
|
|
{
|
|
WARNING(("Version number mismatch"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if (!(bitmapData->Flags & COMPRESSED_IMAGE))
|
|
{
|
|
Bmp = new GpMemoryBitmap();
|
|
|
|
if (Bmp != NULL)
|
|
{
|
|
// Mask out the pixel format
|
|
PixelFormatID pixelFormat = MaskPixelFormat(bitmapData->PixelFormat);
|
|
HRESULT hr = Bmp->InitNewBitmap(bitmapData->Width,
|
|
bitmapData->Height,
|
|
pixelFormat);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
WARNING(("InitNewBitmap failed"));
|
|
delete Bmp;
|
|
Bmp = NULL;
|
|
return GenericError;
|
|
}
|
|
|
|
// Fill image info structure
|
|
|
|
if ( Bmp->GetImageInfo(&SrcImageInfo) != S_OK )
|
|
{
|
|
WARNING(("InitNewBitmap failed"));
|
|
delete Bmp;
|
|
Bmp = NULL;
|
|
return GenericError;
|
|
}
|
|
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
|
|
ASSERT(Bmp->Stride == bitmapData->Stride);
|
|
dataBuffer += sizeof(BitmapRecordData);
|
|
size -= sizeof(BitmapRecordData);
|
|
|
|
State = MemBitmap;
|
|
|
|
// If it's an indexed format, we'll have stored the palette next
|
|
if(IsIndexedPixelFormat(pixelFormat))
|
|
{
|
|
if (size < sizeof(ColorPalette))
|
|
{
|
|
WARNING(("size too small"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
UINT paletteSize;
|
|
ColorPalette *pal;
|
|
pal = (ColorPalette *)dataBuffer;
|
|
|
|
// Work out how big the palette is.
|
|
// sizeof(ColorPalette) includes the first entry.
|
|
paletteSize = sizeof(ColorPalette)+sizeof(ARGB)*(pal->Count-1);
|
|
|
|
if (size < paletteSize)
|
|
{
|
|
WARNING(("size too small"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// Make the GpMemoryBitmap clone the palette into the right place
|
|
Bmp->SetPalette(pal);
|
|
|
|
// Update the dataBuffer stream to the beginning of the pixel data
|
|
dataBuffer += paletteSize;
|
|
size -= paletteSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("Out of memory"));
|
|
return OutOfMemory;
|
|
}
|
|
|
|
ASSERT((Bmp != NULL) && (Bmp->Scan0 != NULL));
|
|
|
|
ULONG pixelSize = Bmp->Stride * Bmp->Height;
|
|
|
|
if (size >= pixelSize)
|
|
{
|
|
size = pixelSize;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("Insufficient data to fill bitmap"));
|
|
status = InvalidParameter;
|
|
}
|
|
|
|
if (size > 0)
|
|
{
|
|
GpMemcpy(Bmp->Scan0, dataBuffer, size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create an IStream on top of the memory buffer
|
|
|
|
GpReadOnlyMemoryStream* stream;
|
|
|
|
stream = new GpReadOnlyMemoryStream();
|
|
|
|
if (!stream)
|
|
{
|
|
WARNING(("Out of memory"));
|
|
return OutOfMemory;
|
|
}
|
|
|
|
dataBuffer += sizeof(BitmapRecordData);
|
|
size -= sizeof(BitmapRecordData);
|
|
|
|
stream->InitBuffer(dataBuffer, size);
|
|
|
|
// since we don't want to hold dataBuffer or make a copy of
|
|
// it, we just Load it into memory here.
|
|
|
|
Stream = stream;
|
|
State = ExtStream;
|
|
|
|
status = LoadIntoMemory();
|
|
|
|
if ( status == Ok )
|
|
{
|
|
// The source image is loaded into memory and we are not going to
|
|
// keep the source image connection any more. So we fill the image
|
|
// info with the memory bits info
|
|
|
|
if ( Bmp->GetImageInfo(&SrcImageInfo) != S_OK )
|
|
{
|
|
status = GenericError;
|
|
}
|
|
else
|
|
{
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
}
|
|
}
|
|
|
|
stream->Release();
|
|
Stream = NULL;
|
|
|
|
if (Img)
|
|
{
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
char ColorChannelName[4] = {'C', 'M', 'Y', 'K'};
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Do the color adjustment if the lower level codec can do it.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] recolor - Pointer to image attributes
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
* Return Ok -------------Lower level does it
|
|
* Return NotImplemented--Lower level can't do it
|
|
* Other status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 11/22/1999 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ColorAdjustByCodec(
|
|
GpRecolor * recolor,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData
|
|
)
|
|
{
|
|
if (recolor == NULL)
|
|
{
|
|
// The lower level codec doesn't know how to handle this, let the
|
|
// up level do it
|
|
|
|
return NotImplemented;
|
|
}
|
|
|
|
HRESULT hResult;
|
|
|
|
UINT uiCurrentFlag = recolor->GetValidFlags(ColorAdjustTypeBitmap);
|
|
BOOL bImgCreatedHere = FALSE;
|
|
|
|
// First we need to check if the current lower level decoder can do the
|
|
// job or not
|
|
|
|
if ( Img == NULL )
|
|
{
|
|
// Create a GpDecodedImage*
|
|
|
|
if ( NULL != Stream )
|
|
{
|
|
hResult = GpDecodedImage::CreateFromStream(Stream, &Img);
|
|
}
|
|
else
|
|
{
|
|
if ( Filename == NULL )
|
|
{
|
|
// We can't continue. Let the higher level do it
|
|
|
|
return NotImplemented;
|
|
}
|
|
|
|
hResult = GpDecodedImage::CreateFromFile(Filename, &Img);
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("Failed to create decoded image: %x", hResult));
|
|
return Win32Error;
|
|
}
|
|
|
|
// Remember that we creat a copy of Img here. Should be freed when done
|
|
|
|
bImgCreatedHere = TRUE;
|
|
}// (Img == NULL)
|
|
|
|
GUID DecoderParamGuid;
|
|
UINT DecoderParamLength;
|
|
PVOID DecoderParamPtr;
|
|
GpStatus rCode = Win32Error;
|
|
|
|
// Set the GUID and other parameters with respect to the image attributes
|
|
// Note: we won't have a recolor which has both ValidColorKeys and
|
|
// ValidOutputChannel set. This function won't be called if this case is
|
|
// TRUE. We checked this in ColorAdjust()
|
|
|
|
UINT value[2];
|
|
if ( uiCurrentFlag & (GpRecolorObject::ValidColorKeys) )
|
|
{
|
|
// Set color key
|
|
|
|
DecoderParamGuid = DECODER_TRANSCOLOR;
|
|
DecoderParamLength = 8;
|
|
|
|
value[0] = (UINT)(recolor->GetColorKeyLow(ColorAdjustTypeBitmap));
|
|
value[1] = (UINT)(recolor->GetColorKeyHigh(ColorAdjustTypeBitmap));
|
|
|
|
DecoderParamPtr = (VOID*)value;
|
|
}
|
|
else if ( uiCurrentFlag & (GpRecolorObject::ValidOutputChannel) )
|
|
{
|
|
// Asks the codec doing a color separation makes sense only when the
|
|
// source image is in CMYK space. Otherwise, we do it in our recolor
|
|
// object which contains a generic algorithem for doing it
|
|
|
|
if ( !( (SrcImageInfo.Flags & ImageFlagsColorSpaceCMYK)
|
|
||(SrcImageInfo.Flags & ImageFlagsColorSpaceYCCK) ) )
|
|
{
|
|
// Not a CMYK image, do it in recolor object
|
|
|
|
rCode = NotImplemented;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Set channel output
|
|
|
|
DecoderParamGuid = DECODER_OUTPUTCHANNEL;
|
|
DecoderParamLength = 1;
|
|
|
|
DecoderParamPtr =
|
|
(VOID*)(&ColorChannelName[recolor->GetChannelIndex(ColorAdjustTypeBitmap)]);
|
|
}
|
|
|
|
// Query to see if the decoder can do it or not
|
|
|
|
hResult = Img->QueryDecoderParam(DecoderParamGuid);
|
|
|
|
if ( (hResult != E_NOTIMPL) && (hResult != S_OK) )
|
|
{
|
|
WARNING(("Failed to query decoder param: %x", hResult));
|
|
goto CleanUp;
|
|
}
|
|
else if ( hResult == E_NOTIMPL )
|
|
{
|
|
// The lower level decoder doesn't support this.
|
|
|
|
rCode = NotImplemented;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Set the decoder param to tell the lower level how to decode
|
|
|
|
hResult = Img->SetDecoderParam(DecoderParamGuid, DecoderParamLength,
|
|
DecoderParamPtr);
|
|
|
|
if ( (hResult != E_NOTIMPL) && (hResult != S_OK) )
|
|
{
|
|
WARNING(("Failed to set decoder param: %x", hResult));
|
|
goto CleanUp;
|
|
}
|
|
else if ( hResult == E_NOTIMPL )
|
|
{
|
|
// The lower level decoder doesn't support this.
|
|
|
|
rCode = NotImplemented;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Now we don't need the previous "Bmp" since we are going to ask the
|
|
// lower level decoder to create one for us
|
|
|
|
if ( Bmp != NULL )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
}
|
|
|
|
// Ask the decoder to create a 32BPP ARGB GpMemoryBitmap.
|
|
|
|
hResult = GpMemoryBitmap::CreateFromImage(Img,
|
|
0,
|
|
0,
|
|
PIXFMT_32BPP_ARGB,
|
|
InterpolationHintDefault,
|
|
&Bmp,
|
|
callback,
|
|
callbackData);
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("Failed to load image into memory: %x", hResult));
|
|
goto CleanUp;
|
|
}
|
|
|
|
State = MemBitmap;
|
|
PixelFormatInMem = PIXFMT_32BPP_ARGB;
|
|
|
|
// The lower level does the job for us.
|
|
|
|
rCode = Ok;
|
|
|
|
CleanUp:
|
|
if ( bImgCreatedHere == TRUE )
|
|
{
|
|
// Note: we don't need to check if Img == NULL or not because this flag
|
|
// would only be set when we successed in creating Img
|
|
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
|
|
return rCode;
|
|
}// ColorAdjustByCodec()
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ColorAdjust(
|
|
GpRecolor * recolor,
|
|
PixelFormatID pixfmt,
|
|
DrawImageAbort callback,
|
|
VOID *callbackData
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
ASSERT(ObjRefCount == 1);
|
|
ASSERT(recolor != NULL);
|
|
|
|
// Mark the result image (color adjusted image) as dirty
|
|
// Note: this won't damage the original source image because we always
|
|
// color adjust on a cloned copy of the original image
|
|
|
|
SetDirtyFlag(TRUE);
|
|
|
|
UINT uiCurrentFlag = recolor->GetValidFlags(ColorAdjustTypeBitmap);
|
|
|
|
// For color key output: we will ask the lower level decoder to do the job
|
|
// if there is no other recoloring flag is specified
|
|
// For color separation(channel output), we will ask the lower level decoder
|
|
// to do the job if there is no other recoloring flag is specified except
|
|
// for ValidColorProfile.
|
|
// If the codec can handle CMYK separation, then the profile is ignored.
|
|
// If the source is RGB image, then ColorAdjustByCodec() will do nothing
|
|
// and we will use the profile to do RGB to CMYK conversion before
|
|
// channel separation
|
|
|
|
if ( uiCurrentFlag
|
|
&&( ((uiCurrentFlag & GpRecolorObject::ValidColorKeys) == uiCurrentFlag)
|
|
||((uiCurrentFlag
|
|
&(~GpRecolorObject::ValidChannelProfile)
|
|
&(GpRecolorObject::ValidOutputChannel))
|
|
== (GpRecolorObject::ValidOutputChannel) ) ) )
|
|
{
|
|
Status rCode = ColorAdjustByCodec(recolor, callback, callbackData);
|
|
|
|
if ( rCode != NotImplemented )
|
|
{
|
|
// Either the lower level did the job for us (rCode == Ok) or it
|
|
// failed somehow (rCode == Win32Error etc.). We just return here
|
|
|
|
return rCode;
|
|
}
|
|
|
|
// Lower level can't do it. We can just slip here to do the normal
|
|
// software version of color adjust
|
|
|
|
}// Color key and color channel handling
|
|
|
|
GpStatus status = LoadIntoMemory(pixfmt);
|
|
|
|
if ( status != Ok )
|
|
{
|
|
return status;
|
|
}
|
|
|
|
hr = Bmp->PerformColorAdjustment(recolor,
|
|
ColorAdjustTypeBitmap,
|
|
callback, callbackData);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
Bmp->SetAlphaHint(GpMemoryBitmap::ALPHA_UNKNOWN);
|
|
return Ok;
|
|
}
|
|
else if (hr == IMGERR_ABORT)
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::ColorAdjust---Aborted"));
|
|
return Aborted;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::ColorAdjust---PerformColorAdjustment() failed"));
|
|
return GenericError;
|
|
}
|
|
}// ColorAdjust()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the (current) pixel format of the CopyOnWriteBitmap.
|
|
* Here "current" means the pixel format in the memory if it has been loaded,
|
|
* aka, a GpMemoryBitmap. If it is not in the memory, then we return the
|
|
* PixelFormat of the original image
|
|
*
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [OUT] pixfmt - Pointer to pixel format
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
* Ok - success
|
|
* Win32Error - failed
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 06/10/2000 asecchia
|
|
* Created it.
|
|
* 07/26/2000 minliu
|
|
* Re-wrote it
|
|
*
|
|
\**************************************************************************/
|
|
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetPixelFormatID(
|
|
PixelFormatID *pixfmt
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// If the image is in the memory, then we return the memory bitmap pixel
|
|
// format. Otherwise, return the source image format
|
|
|
|
if ( (State == MemBitmap) && (PixelFormatInMem != PixelFormatDontCare) )
|
|
{
|
|
*pixfmt = PixelFormatInMem;
|
|
}
|
|
else
|
|
{
|
|
*pixfmt = SrcImageInfo.PixelFormat;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
CopyOnWriteBitmap*
|
|
CopyOnWriteBitmap::CloneColorAdjusted(
|
|
GpRecolor * recolor,
|
|
ColorAdjustType type
|
|
) const
|
|
{
|
|
ASSERT(recolor != NULL);
|
|
|
|
CopyOnWriteBitmap * clonedBitmap = (CopyOnWriteBitmap *)this->Clone();
|
|
|
|
if (clonedBitmap != NULL)
|
|
{
|
|
if ((clonedBitmap->IsValid()) &&
|
|
(clonedBitmap->ColorAdjust(recolor, type) == Ok))
|
|
{
|
|
clonedBitmap->SetDirtyFlag(TRUE);
|
|
return clonedBitmap;
|
|
}
|
|
delete clonedBitmap;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::ColorAdjust(
|
|
GpRecolor * recolor,
|
|
ColorAdjustType type
|
|
)
|
|
{
|
|
ASSERT(recolor != NULL);
|
|
ASSERT(ObjRefCount == 1);
|
|
|
|
GpStatus status = LoadIntoMemory();
|
|
|
|
if (status != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
if (type == ColorAdjustTypeDefault)
|
|
{
|
|
type = ColorAdjustTypeBitmap;
|
|
}
|
|
|
|
HRESULT hr = Bmp->PerformColorAdjustment(recolor, type, NULL, NULL);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
SetDirtyFlag(TRUE);
|
|
return Ok;
|
|
}
|
|
|
|
return GenericError;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Override the native resolution of the bitmap.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* xdpi, ydpi - New resolution
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetResolution(
|
|
REAL xdpi,
|
|
REAL ydpi
|
|
)
|
|
{
|
|
if ( (xdpi > 0.0) && (ydpi > 0.0) )
|
|
{
|
|
XDpiOverride = xdpi;
|
|
YDpiOverride = ydpi;
|
|
|
|
if ( Img )
|
|
{
|
|
Img->SetResolution(xdpi, ydpi);
|
|
}
|
|
|
|
if ( Bmp )
|
|
{
|
|
Bmp->SetResolution(xdpi, ydpi);
|
|
}
|
|
|
|
SrcImageInfo.Xdpi = xdpi;
|
|
SrcImageInfo.Ydpi = ydpi;
|
|
|
|
// Mark the bits dirty since we have to save the image with the new
|
|
// resolution info.
|
|
|
|
SetDirtyFlag(TRUE);
|
|
|
|
return Ok;
|
|
}
|
|
else
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
}// SetResolution()
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create a GDI bitmap (HBITMAP) from a GDI+ bitmap.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* phbm -- Return HBITMAP via this pointer
|
|
* background -- If GDI+ bitmap has alpha, blend with this color as the
|
|
* background
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::CreateHBITMAP(HBITMAP *phbm, ARGB background)
|
|
{
|
|
GpStatus status;
|
|
|
|
// These objects need cleanup:
|
|
|
|
HDC hdc = NULL;
|
|
HBITMAP hbmOld = NULL;
|
|
HBITMAP hbmNew = NULL;
|
|
HBRUSH hbr = NULL;
|
|
HBRUSH hbrOld = NULL;
|
|
GpGraphics *g = NULL;
|
|
|
|
// Get format information for this bitmap:
|
|
|
|
// Create HDC:
|
|
|
|
hdc = CreateCompatibleDC(NULL);
|
|
if (!hdc)
|
|
{
|
|
WARNING(("CreateHBITMAP: CreateCompatibleDC failed"));
|
|
status = Win32Error;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// Create DIB section:
|
|
|
|
BITMAPINFO bmi;
|
|
VOID *pv;
|
|
|
|
GpMemset(&bmi, 0, sizeof(bmi));
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = SrcImageInfo.Width;
|
|
bmi.bmiHeader.biHeight = SrcImageInfo.Height;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
hbmNew = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pv, NULL, 0);
|
|
if (!hbmNew)
|
|
{
|
|
WARNING(("CreateHBITMAP: CreateDIBSection failed\n"));
|
|
status = Win32Error;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// Select DIB into DC:
|
|
|
|
hbmOld = (HBITMAP) SelectObject(hdc, hbmNew);
|
|
if (!hbmOld)
|
|
{
|
|
WARNING(("CreateHBITMAP: SelectObject(hbm) failed\n"));
|
|
status = Win32Error;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// Clear DIB to specified ARGB color:
|
|
|
|
LOGBRUSH lbr;
|
|
|
|
lbr.lbStyle = BS_SOLID;
|
|
lbr.lbColor = RGB(background & 0x00ff0000,
|
|
background & 0x0000ff00,
|
|
background & 0x000000ff);
|
|
|
|
hbr = CreateBrushIndirect(&lbr);
|
|
if (!hbr)
|
|
{
|
|
WARNING(("CreateHBITMAP: CreateBrushIndirect failed\n"));
|
|
status = Win32Error;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
hbrOld = (HBRUSH) SelectObject(hdc, hbr);
|
|
if (!hbrOld)
|
|
{
|
|
WARNING(("CreateHBITMAP: SelectObject(hbr) failed\n"));
|
|
status = Win32Error;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
PatBlt(hdc, 0, 0, SrcImageInfo.Width, SrcImageInfo.Height, PATCOPY);
|
|
|
|
// Derive Graphics from HDC:
|
|
|
|
g = GpGraphics::GetFromHdc(hdc);
|
|
if (!g)
|
|
{
|
|
WARNING(("CreateHBITMAP: GpGraphics::GetFromHdc failed\n"));
|
|
status = OutOfMemory;
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// DrawImage bitmap to Graphics:
|
|
|
|
{
|
|
GpLock lock(g->GetObjectLock());
|
|
if (lock.IsValid())
|
|
{
|
|
FPUStateSaver fpState;
|
|
|
|
GpRectF rect(0.0, 0.0, TOREAL(SrcImageInfo.Width),
|
|
TOREAL(SrcImageInfo.Height));
|
|
GpBitmap tmpBitmap(this);
|
|
status = g->DrawImage(&tmpBitmap, rect, rect, UnitPixel);
|
|
|
|
if (status == Ok)
|
|
{
|
|
// Bypass cleanup of the bitmap, we want to keep it:
|
|
|
|
*phbm = hbmNew;
|
|
hbmNew = NULL;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("CreateHBITMAP: GpGraphics::DrawImage failed"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = ObjectBusy;
|
|
}
|
|
}
|
|
|
|
error_cleanup:
|
|
|
|
if (hdc)
|
|
{
|
|
if (hbmOld)
|
|
SelectObject(hdc, hbmOld);
|
|
|
|
if (hbrOld)
|
|
SelectObject(hdc, hbrOld);
|
|
|
|
DeleteDC(hdc);
|
|
}
|
|
|
|
if (hbmNew)
|
|
DeleteObject(hbmNew);
|
|
|
|
if (hbr)
|
|
DeleteObject(hbr);
|
|
|
|
if (g)
|
|
delete g;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* INTEROP
|
|
*
|
|
* Create a Win32 icon (HICON) from a GDI+ bitmap.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* phicon -- Return HICON via this pointer
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID ExportMask32BPP(BitmapData* mask, BitmapData* src)
|
|
{
|
|
ASSERT(src->PixelFormat == PIXFMT_32BPP_ARGB);
|
|
ASSERT(mask->PixelFormat == PIXFMT_32BPP_RGB);
|
|
ASSERT(src->Width == mask->Width);
|
|
ASSERT(src->Height == mask->Height);
|
|
ASSERT(src->Scan0 != NULL);
|
|
ASSERT(mask->Scan0 != NULL);
|
|
|
|
BYTE* srcScan = static_cast<BYTE*>(src->Scan0);
|
|
BYTE* maskScan = static_cast<BYTE*>(mask->Scan0);
|
|
|
|
for (UINT row = 0; row < src->Height; row++)
|
|
{
|
|
ARGB *srcPixel = static_cast<ARGB*>(static_cast<VOID*>(srcScan));
|
|
ARGB *maskPixel = static_cast<ARGB*>(static_cast<VOID*>(maskScan));
|
|
|
|
for (UINT col = 0; col < src->Width; col++)
|
|
{
|
|
if ((*srcPixel & 0xff000000) == 0xff000000)
|
|
*maskPixel = 0; // Opaque
|
|
else
|
|
*maskPixel = 0x00ffffff; // Transparent
|
|
|
|
srcPixel++;
|
|
maskPixel++;
|
|
}
|
|
|
|
srcScan = srcScan + src->Stride;
|
|
maskScan = maskScan + mask->Stride;
|
|
}
|
|
}
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::CreateHICON(
|
|
HICON *phicon
|
|
)
|
|
{
|
|
GpStatus status = Win32Error;
|
|
|
|
ICONINFO iconInfo;
|
|
|
|
iconInfo.fIcon = TRUE;
|
|
|
|
status = CreateHBITMAP(&iconInfo.hbmColor, 0);
|
|
|
|
if (status == Ok)
|
|
{
|
|
BitmapData bmpDataSrc;
|
|
|
|
status = this->LockBits(NULL,
|
|
IMGLOCK_READ,
|
|
PIXFMT_32BPP_ARGB,
|
|
&bmpDataSrc);
|
|
|
|
if (status == Ok)
|
|
{
|
|
// From this point on, assume failure until we succeed:
|
|
|
|
status = Win32Error;
|
|
|
|
// Create empty bitmap for the icon mask:
|
|
|
|
iconInfo.hbmMask = CreateBitmap(bmpDataSrc.Width,
|
|
bmpDataSrc.Height,
|
|
1, 1, NULL);
|
|
|
|
if (iconInfo.hbmMask != NULL)
|
|
{
|
|
VOID *gdiBitmapData = GpMalloc(bmpDataSrc.Width
|
|
* bmpDataSrc.Height
|
|
* 4);
|
|
|
|
if (gdiBitmapData)
|
|
{
|
|
// Convert alpha channel into a 32bpp DIB mask:
|
|
|
|
BitmapData bmpDataMask;
|
|
|
|
bmpDataMask.Width = bmpDataSrc.Width;
|
|
bmpDataMask.Height = bmpDataSrc.Height;
|
|
bmpDataMask.Stride = bmpDataSrc.Width * 4;
|
|
bmpDataMask.PixelFormat = PIXFMT_32BPP_RGB;
|
|
bmpDataMask.Scan0 = gdiBitmapData;
|
|
bmpDataMask.Reserved = 0;
|
|
|
|
ExportMask32BPP(&bmpDataMask, &bmpDataSrc);
|
|
|
|
// Set mask bits:
|
|
|
|
BYTE bufferBitmapInfo[sizeof(BITMAPINFO)];
|
|
BITMAPINFO *gdiBitmapInfo = (BITMAPINFO *) bufferBitmapInfo;
|
|
|
|
memset(bufferBitmapInfo, 0, sizeof(bufferBitmapInfo));
|
|
gdiBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
|
|
gdiBitmapInfo->bmiHeader.biWidth = bmpDataSrc.Width;
|
|
gdiBitmapInfo->bmiHeader.biHeight = - static_cast<LONG>
|
|
(bmpDataSrc.Height);
|
|
gdiBitmapInfo->bmiHeader.biPlanes = 1;
|
|
gdiBitmapInfo->bmiHeader.biBitCount = 32;
|
|
gdiBitmapInfo->bmiHeader.biCompression = BI_RGB;
|
|
|
|
HDC hdc = GetDC(NULL);
|
|
|
|
if (hdc != NULL)
|
|
{
|
|
SetTextColor(hdc, RGB(0, 0, 0));
|
|
SetBkColor(hdc, RGB(0xff, 0xff, 0xff));
|
|
SetBkMode(hdc, OPAQUE);
|
|
|
|
if (SetDIBits(hdc,
|
|
iconInfo.hbmMask,
|
|
0,
|
|
bmpDataSrc.Height,
|
|
gdiBitmapData,
|
|
gdiBitmapInfo,
|
|
DIB_RGB_COLORS
|
|
))
|
|
{
|
|
// Create icon:
|
|
|
|
*phicon = CreateIconIndirect(&iconInfo);
|
|
|
|
if (*phicon != NULL)
|
|
status = Ok;
|
|
else
|
|
{
|
|
WARNING(("CreateIconIndirect failed"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("SetDIBits failed"));
|
|
}
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
|
|
GpFree(gdiBitmapData);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("memory allocation failed"));
|
|
status = OutOfMemory;
|
|
}
|
|
|
|
DeleteObject(iconInfo.hbmMask);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("CreateBitmap failed"));
|
|
}
|
|
|
|
this->UnlockBits(&bmpDataSrc);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("LockBits failed"));
|
|
}
|
|
|
|
DeleteObject(iconInfo.hbmColor);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Prep the bitmap for drawing.
|
|
*
|
|
* Currently, the only thing we do is check if the bitmap is an ICON.
|
|
* If so, we set the DECODER_ICONRES parameters if supported.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* numPoints
|
|
* dstPoints Specifies the destination area
|
|
*
|
|
* srcRect Specifies the source area
|
|
*
|
|
* numBitsPerPixel Specifies the bits-per-pixel of the destination
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::PreDraw(
|
|
INT numPoints,
|
|
GpPointF *dstPoints,
|
|
GpRectF *srcRect,
|
|
INT numBitsPerPixel
|
|
)
|
|
{
|
|
// Check if ICON:
|
|
|
|
GpStatus status = Ok;
|
|
|
|
if ( SrcImageInfo.RawDataFormat == IMGFMT_ICO )
|
|
{
|
|
status = SetIconParameters(numPoints, dstPoints, srcRect,
|
|
numBitsPerPixel);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set the decode parameters for multi-resolution icons.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* numPoints
|
|
* dstPoints Specifies the destination area
|
|
*
|
|
* srcRect Specifies the source area
|
|
*
|
|
* numBitsPerPixel Specifies the bits-per-pixel of the destination
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetIconParameters(
|
|
INT numPoints,
|
|
GpPointF *dstPoints,
|
|
GpRectF *srcRect,
|
|
INT numBitsPerPixel
|
|
)
|
|
{
|
|
// Check if DECODER_ICONRES supported:
|
|
|
|
HRESULT hResult;
|
|
|
|
BOOL imageCleanupNeeded = FALSE;
|
|
|
|
// First we need to check if the current lower level decoder can do the
|
|
// job or not
|
|
|
|
if (Img == NULL)
|
|
{
|
|
// Create a GpDecodedImage*
|
|
|
|
if (NULL != Stream)
|
|
{
|
|
hResult = GpDecodedImage::CreateFromStream(Stream, &Img);
|
|
}
|
|
else
|
|
{
|
|
if (Filename == NULL)
|
|
{
|
|
// We can't continue. Let the higher level do it
|
|
|
|
return GenericError;
|
|
}
|
|
|
|
hResult = GpDecodedImage::CreateFromFile(Filename, &Img);
|
|
}
|
|
|
|
if (FAILED(hResult))
|
|
{
|
|
WARNING(("Failed to create decoded image: %x", hResult));
|
|
return Win32Error;
|
|
}
|
|
|
|
// Remember that we creat a copy of Img here. Should be freed when done
|
|
|
|
imageCleanupNeeded = TRUE;
|
|
}
|
|
|
|
GpStatus status = Win32Error;
|
|
|
|
// Query to see if the decoder can do it or not
|
|
|
|
hResult = Img->QueryDecoderParam(DECODER_ICONRES);
|
|
|
|
if (hResult != S_OK)
|
|
{
|
|
if ((hResult == E_FAIL) || (hResult == E_NOTIMPL))
|
|
{
|
|
// Decoder doesn't want it, which is OK.
|
|
|
|
status = Ok;
|
|
goto CleanUp;
|
|
}
|
|
else
|
|
{
|
|
// Something else is wrong
|
|
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
// Setup the GUID and decode parameters
|
|
|
|
{
|
|
UINT value[3];
|
|
value[0] = static_cast<UINT>
|
|
(GetDistance(dstPoints[0], dstPoints[1]) + 0.5);
|
|
value[1] = static_cast<UINT>
|
|
(GetDistance(dstPoints[0], dstPoints[2]) + 0.5);
|
|
value[2] = numBitsPerPixel;
|
|
|
|
UINT DecoderParamLength = 3*sizeof(UINT);
|
|
PVOID DecoderParamPtr = (VOID*) value;
|
|
|
|
// Set the decoder param to tell the lower level how to decode
|
|
|
|
hResult = Img->SetDecoderParam(DECODER_ICONRES,
|
|
DecoderParamLength,
|
|
DecoderParamPtr);
|
|
}
|
|
|
|
if (hResult != S_OK)
|
|
{
|
|
if ((hResult == E_FAIL) || (hResult == E_NOTIMPL))
|
|
{
|
|
// Decoder doesn't want it, which is OK.
|
|
|
|
status = Ok;
|
|
goto CleanUp;
|
|
}
|
|
else
|
|
{
|
|
// Something else is wrong
|
|
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
// Now we don't need the previous "Bmp" since we are going to ask the
|
|
// lower level decoder to create one for us
|
|
|
|
if ( Bmp != NULL )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
}
|
|
|
|
// Ask the decoder to create a 32BPP ARGB GpMemoryBitmap.
|
|
|
|
hResult = GpMemoryBitmap::CreateFromImage(Img,
|
|
0,
|
|
0,
|
|
PIXFMT_32BPP_ARGB,
|
|
InterpolationHintDefault,
|
|
&Bmp,
|
|
NULL,
|
|
NULL);
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("Failed to load image into memory: %x", hResult));
|
|
goto CleanUp;
|
|
}
|
|
|
|
State = MemBitmap;
|
|
PixelFormatInMem = PIXFMT_32BPP_ARGB;
|
|
|
|
// The lower level does the job for us.
|
|
|
|
status = Ok;
|
|
|
|
CleanUp:
|
|
|
|
if ((status == Ok) && (srcRect != NULL))
|
|
{
|
|
// Icons are not allowed to clip
|
|
|
|
srcRect->X = 0;
|
|
srcRect->Y = 0;
|
|
srcRect->Width = (REAL) SrcImageInfo.Width;
|
|
srcRect->Height = (REAL) SrcImageInfo.Height;
|
|
}
|
|
|
|
if (imageCleanupNeeded == TRUE)
|
|
{
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the transparency state of the bitmap
|
|
*
|
|
* Arguments:
|
|
*
|
|
* transparency Returned state
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetTransparencyHint(
|
|
DpTransparency* transparency
|
|
)
|
|
{
|
|
GpStatus status = GenericError;
|
|
|
|
if (Bmp != NULL)
|
|
{
|
|
INT alphaHint;
|
|
|
|
HRESULT hr = Bmp->GetAlphaHint(&alphaHint);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// It's unfortunate that GpMemoryBitmap does not have
|
|
// a DpTransparency flag internally, but there's a conflict
|
|
// with imaging.dll and the include file structure that for
|
|
// now makes it necessary to keep a separate type for this info.
|
|
|
|
// In fact, ultimately it would be best if the overlap
|
|
// between DpBitmap and GpMemoryBitmap is resolved including
|
|
// a DpBitmap structure within the GpMemoryBitmap and then
|
|
// removing the redundant info out GpMemoryBitmap.
|
|
|
|
switch (alphaHint)
|
|
{
|
|
case GpMemoryBitmap::ALPHA_SIMPLE:
|
|
*transparency = TransparencySimple;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_OPAQUE:
|
|
*transparency = TransparencyOpaque;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_NONE:
|
|
*transparency = TransparencyNoAlpha;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_COMPLEX:
|
|
*transparency = TransparencyComplex;
|
|
break;
|
|
|
|
default:
|
|
*transparency = TransparencyUnknown;
|
|
break;
|
|
}
|
|
|
|
status = Ok;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SUCCEEDED(hr));
|
|
*transparency = TransparencyUnknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*transparency = TransparencyUnknown;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the transparency state of the bitmap. This routine returns accurate
|
|
* info while GetTransparencyHint just returns a hint
|
|
*
|
|
* This routine could scan the whole bitmap (32bpp) so whoever uses it should
|
|
* consider the perf hit.
|
|
*
|
|
* This is currently only for use by the printer drivers.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* transparency Returned state
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
CopyOnWriteBitmap::GetTransparencyFlags(
|
|
DpTransparency* transparency,
|
|
PixelFormatID loadFormat,
|
|
BYTE* minA,
|
|
BYTE* maxA
|
|
)
|
|
{
|
|
GpStatus status = GenericError;
|
|
|
|
ARGB argb;
|
|
ARGB minAlpha = 0xff000000;
|
|
ARGB maxAlpha = 0;
|
|
|
|
INT newAlphaHint = GpMemoryBitmap::ALPHA_OPAQUE;
|
|
|
|
TestBmp:
|
|
if (Bmp != NULL)
|
|
{
|
|
INT alphaHint;
|
|
|
|
// the alpha transparency could change if the bitmap has changed
|
|
|
|
HRESULT hr = Bmp->GetAlphaHint(&alphaHint);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// It's unfortunate that GpMemoryBitmap does not have
|
|
// a DpTransparency flag internally, but there's a conflict
|
|
// with imaging.dll and the include file structure that for
|
|
// now makes it necessary to keep a separate type for this info.
|
|
|
|
// In fact, ultimately it would be best if the overlap
|
|
// between DpBitmap and GpMemoryBitmap is resolved including
|
|
// a DpBitmap structure within the GpMemoryBitmap and then
|
|
// removing the redundant info out GpMemoryBitmap.
|
|
|
|
switch (alphaHint)
|
|
{
|
|
case GpMemoryBitmap::ALPHA_SIMPLE:
|
|
*transparency = TransparencySimple;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_OPAQUE:
|
|
*transparency = TransparencyOpaque;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_NONE:
|
|
*transparency = TransparencyNoAlpha;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_COMPLEX:
|
|
*transparency = TransparencyComplex;
|
|
break;
|
|
|
|
case GpMemoryBitmap::ALPHA_NEARCONSTANT:
|
|
*transparency = TransparencyNearConstant;
|
|
|
|
if (minA != NULL && maxA != NULL)
|
|
{
|
|
// if the flag is nearconstant alpha, we must have got valid min and max alpha
|
|
Bmp->GetMinMaxAlpha(minA, maxA);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
*transparency = TransparencyUnknown;
|
|
break;
|
|
}
|
|
|
|
status = Ok;
|
|
|
|
// printing needs more accuarate info and is always loaded into memory
|
|
// before send down to the printer drivers
|
|
|
|
// 16bpp1555 is handled at initialization already.
|
|
|
|
// TransparencyUnknown means the bitmap can have alpha we just don't know what we have
|
|
// TransparencyNoAlpha means the bitmap format doesn't support alpha
|
|
|
|
if (*transparency == TransparencyUnknown)
|
|
{
|
|
if (IsAlphaPixelFormat(Bmp->PixelFormat))
|
|
{
|
|
// the memory bitmap must be locked before we enter here
|
|
// We don't require the object to be locked. The object should be
|
|
// decoded already in memory. This is true in the case of DrvDrawImage
|
|
// and texture brush images.
|
|
//ASSERT(ObjRefCount > 1);
|
|
*transparency = TransparencyOpaque;
|
|
|
|
|
|
if ((Bmp->PixelFormat == PIXFMT_32BPP_ARGB) || (Bmp->PixelFormat == PIXFMT_32BPP_PARGB))
|
|
{
|
|
*transparency = TransparencyOpaque;
|
|
|
|
UINT x, y;
|
|
|
|
BYTE *scanStart = static_cast<BYTE *>(Bmp->Scan0);
|
|
ARGB *scanPtr;
|
|
|
|
for (y = 0; y < Bmp->Height; y++)
|
|
{
|
|
scanPtr = reinterpret_cast<ARGB *>(scanStart);
|
|
|
|
for (x = 0; x < Bmp->Width; x++)
|
|
{
|
|
argb = (*scanPtr++) & 0xff000000;
|
|
|
|
if (argb < minAlpha)
|
|
{
|
|
minAlpha = argb;
|
|
}
|
|
|
|
if (argb > maxAlpha)
|
|
{
|
|
maxAlpha = argb;
|
|
}
|
|
|
|
if (argb != 0xff000000)
|
|
{
|
|
if (argb == 0)
|
|
{
|
|
// Prefast bug 518296 - the condition below is always true, this is definitely a bug
|
|
// and the || must be replaced with &&. We have no precedent however that this
|
|
// causes customer problems, hence it does not meet the SP bar.
|
|
/*
|
|
if ((*transparency != TransparencyComplex) ||
|
|
(*transparency != TransparencyNearConstant))
|
|
*/
|
|
{
|
|
*transparency = TransparencySimple;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((maxAlpha - minAlpha) <= (NEARCONSTANTALPHA << 24))
|
|
{
|
|
*transparency = TransparencyNearConstant;
|
|
}
|
|
else
|
|
{
|
|
*transparency = TransparencyComplex;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scanStart += Bmp->Stride;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
RIP(("TransparencyUnknown returned for pixel format w/ alpha"));
|
|
|
|
goto done;
|
|
}
|
|
}
|
|
else if (IsIndexedPixelFormat(Bmp->PixelFormat) &&
|
|
Bmp->colorpal)
|
|
{
|
|
// Compute transparancy hint from palette
|
|
// if we worry about cases
|
|
// that the transparent index is not used in the bitmap
|
|
// then we have to scan the whole bitmap.
|
|
// I believe this is sufficient for now unless someone
|
|
// run into problems that really need to scan the whole bitmap
|
|
|
|
*transparency = TransparencyOpaque;
|
|
|
|
for (UINT i = 0; i < Bmp->colorpal->Count; i++)
|
|
{
|
|
argb = Bmp->colorpal->Entries[i] & 0xff000000;
|
|
|
|
if (argb < minAlpha)
|
|
{
|
|
minAlpha = argb;
|
|
}
|
|
|
|
if (argb > maxAlpha)
|
|
{
|
|
maxAlpha = argb;
|
|
}
|
|
|
|
if (argb != 0xff000000)
|
|
{
|
|
if (argb == 0)
|
|
{
|
|
// See the comments above.
|
|
/*
|
|
if ((*transparency != TransparencyComplex) ||
|
|
(*transparency != TransparencyNearConstant))
|
|
*/
|
|
{
|
|
*transparency = TransparencySimple;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((maxAlpha - minAlpha) <= (NEARCONSTANTALPHA << 24))
|
|
{
|
|
*transparency = TransparencyNearConstant;
|
|
}
|
|
else
|
|
{
|
|
*transparency = TransparencyComplex;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
// Native pixel format does not support alpha
|
|
*transparency = TransparencyNoAlpha;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*transparency = TransparencyUnknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = LoadIntoMemory(loadFormat);
|
|
|
|
if (status == Ok)
|
|
{
|
|
ASSERT(Bmp != NULL);
|
|
goto TestBmp;
|
|
}
|
|
|
|
*transparency = TransparencyUnknown;
|
|
}
|
|
|
|
return status;
|
|
|
|
done:
|
|
// Set alpha hint back into GpMemoryBitmap
|
|
// so we don't have to scan the bitmap again later
|
|
|
|
if (*transparency == TransparencySimple)
|
|
{
|
|
newAlphaHint = GpMemoryBitmap::ALPHA_SIMPLE;
|
|
}
|
|
else if (*transparency == TransparencyComplex)
|
|
{
|
|
newAlphaHint = GpMemoryBitmap::ALPHA_COMPLEX;
|
|
}
|
|
else if (*transparency == TransparencyNearConstant)
|
|
{
|
|
if (minA != NULL && maxA != NULL)
|
|
{
|
|
*minA = (BYTE)(minAlpha >> 24);
|
|
*maxA = (BYTE)(maxAlpha >> 24);
|
|
}
|
|
|
|
Bmp->SetMinMaxAlpha((BYTE)(minAlpha >> 24), (BYTE)(maxAlpha >> 24));
|
|
|
|
newAlphaHint = GpMemoryBitmap::ALPHA_NEARCONSTANT;
|
|
}
|
|
else if (*transparency == TransparencyOpaque)
|
|
{
|
|
newAlphaHint = GpMemoryBitmap::ALPHA_OPAQUE;
|
|
}
|
|
|
|
Bmp->SetAlphaHint(newAlphaHint);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set the transparency state of the bitmap
|
|
*
|
|
* Arguments:
|
|
*
|
|
* transparency Returned state
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::SetTransparencyHint(
|
|
DpTransparency transparency
|
|
)
|
|
{
|
|
GpStatus status = GenericError;
|
|
|
|
if (Bmp != NULL)
|
|
{
|
|
INT alphaHint;
|
|
|
|
// It's unfortunate that GpMemoryBitmap does not have
|
|
// a DpTransparency flag internally, but there's a conflict
|
|
// with imaging.dll and the include file structure that for
|
|
// now makes it necessary to keep a separate type for this info.
|
|
|
|
// In fact, ultimately it would be best if the overlap
|
|
// between DpBitmap and GpMemoryBitmap is resolved including
|
|
// a DpBitmap structure within the GpMemoryBitmap and then
|
|
// removing the redundant info out GpMemoryBitmap.
|
|
|
|
switch (transparency)
|
|
{
|
|
case TransparencySimple:
|
|
alphaHint = GpMemoryBitmap::ALPHA_SIMPLE;
|
|
break;
|
|
|
|
case TransparencyOpaque:
|
|
alphaHint = GpMemoryBitmap::ALPHA_OPAQUE;
|
|
break;
|
|
|
|
case TransparencyNoAlpha:
|
|
alphaHint = GpMemoryBitmap::ALPHA_NONE;
|
|
break;
|
|
|
|
case TransparencyUnknown:
|
|
alphaHint = GpMemoryBitmap::ALPHA_UNKNOWN;
|
|
break;
|
|
|
|
default:
|
|
alphaHint = GpMemoryBitmap::ALPHA_COMPLEX;
|
|
break;
|
|
}
|
|
|
|
HRESULT hr = Bmp->SetAlphaHint(alphaHint);
|
|
|
|
if (SUCCEEDED(hr))
|
|
status = Ok;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Rotate and Flip the image in memory.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN]rfType -- Rotate and Flip type
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 10/06/2000 minliu
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
CopyOnWriteBitmap::RotateFlip(
|
|
RotateFlipType rfType
|
|
)
|
|
{
|
|
if ( rfType == RotateNoneFlipNone )
|
|
{
|
|
// Same for Rotate180FlipXY, this is a No-OP
|
|
|
|
return Ok;
|
|
}
|
|
|
|
if ( (IsDirty() == FALSE)
|
|
&&(State >= MemBitmap)
|
|
&&(SrcImageInfo.PixelFormat != PixelFormatInMem)
|
|
&&(Img != NULL) )
|
|
{
|
|
// If the image is:
|
|
// 1) Not dirty
|
|
// 2) Was loaded into memory with different color depth for some reason,
|
|
// like DrawImage()
|
|
// 3) We have an source image
|
|
//
|
|
// Then we can throw away the bits in memory and reload the bits from
|
|
// the original. The purpose of this is that we should always do Rotate
|
|
// or Flip on the original image.
|
|
|
|
ASSERT( Bmp != NULL )
|
|
Bmp->Release();
|
|
Bmp = NULL;
|
|
State = DecodedImg;
|
|
PixelFormatInMem = PixelFormatUndefined;
|
|
}
|
|
|
|
// Rotate and Flip OP can only be done in memory
|
|
// If the image hasn't been loaded, load into memory with the original
|
|
// pixel format
|
|
|
|
GpStatus status = LoadIntoMemory(SrcImageInfo.PixelFormat);
|
|
if ( status != Ok )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---LoadIntoMemory() failed"));
|
|
return status;
|
|
}
|
|
|
|
IBitmapImage* newBmp = NULL;
|
|
|
|
HRESULT hResult = S_OK;
|
|
|
|
switch ( rfType )
|
|
{
|
|
case Rotate90FlipNone:
|
|
// Rotate270FlipXY = Rotate90FlipNone
|
|
|
|
hResult = Bmp->Rotate(90, INTERP_DEFAULT, &newBmp);
|
|
break;
|
|
|
|
case Rotate180FlipNone:
|
|
// RotateNoneFlipXY = Rotate180FlipNone
|
|
|
|
hResult = Bmp->Rotate(180, INTERP_DEFAULT, &newBmp);
|
|
break;
|
|
|
|
case Rotate270FlipNone:
|
|
// Rotate90FlipXY
|
|
|
|
hResult = Bmp->Rotate(270, INTERP_DEFAULT, &newBmp);
|
|
break;
|
|
|
|
case RotateNoneFlipX:
|
|
// Rotate180FlipY = RotateNoneFlipX
|
|
|
|
hResult = Bmp->Flip(TRUE, FALSE, &newBmp);
|
|
break;
|
|
|
|
case Rotate90FlipX:
|
|
// Rotate270FlipY = Rotate90FlipX
|
|
|
|
hResult = Bmp->Rotate(90, INTERP_DEFAULT, &newBmp);
|
|
if ( SUCCEEDED(hResult) )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = (GpMemoryBitmap*)newBmp;
|
|
|
|
hResult = Bmp->Flip(TRUE, FALSE, &newBmp);
|
|
}
|
|
|
|
break;
|
|
|
|
case Rotate180FlipX:
|
|
// RotateNoneFlipY = Rotate180FlipX
|
|
|
|
hResult = Bmp->Rotate(180, INTERP_DEFAULT, &newBmp);
|
|
if ( SUCCEEDED(hResult) )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = (GpMemoryBitmap*)newBmp;
|
|
|
|
hResult = Bmp->Flip(TRUE, FALSE, &newBmp);
|
|
}
|
|
|
|
break;
|
|
|
|
case Rotate270FlipX:
|
|
// Rotate90FlipY = Rotate270FlipX
|
|
|
|
hResult = Bmp->Rotate(270, INTERP_DEFAULT, &newBmp);
|
|
if ( SUCCEEDED(hResult) )
|
|
{
|
|
Bmp->Release();
|
|
Bmp = (GpMemoryBitmap*)newBmp;
|
|
|
|
hResult = Bmp->Flip(TRUE, FALSE, &newBmp);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---Invalid input parameter"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if ( FAILED(hResult) )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---Rotate failed"));
|
|
return Win32Error;
|
|
}
|
|
|
|
// Check how many property items in this image
|
|
|
|
UINT uiNumOfProperty = 0;
|
|
status = GetPropertyCount(&uiNumOfProperty);
|
|
|
|
if ( status != Ok )
|
|
{
|
|
// It is OK if we failed to get property. We still have the Rotate/Flip
|
|
// result
|
|
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---GetPropertyCount() failed"));
|
|
}
|
|
|
|
if ( uiNumOfProperty > 0 )
|
|
{
|
|
PROPID* pList = (PROPID*)GpMalloc(uiNumOfProperty * sizeof(PROPID));
|
|
if ( pList == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---GpMalloc() failed"));
|
|
return OutOfMemory;
|
|
}
|
|
|
|
status = GetPropertyIdList(uiNumOfProperty, pList);
|
|
if ( status != Ok )
|
|
{
|
|
WARNING(("COnWriteBitmap::RotateFlip-GetPropertyIdList() failed"));
|
|
GpFree(pList);
|
|
return status;
|
|
}
|
|
|
|
UINT uiItemSize = 0;
|
|
PropertyItem* pItem = NULL;
|
|
|
|
GpMemoryBitmap* pTempBmp = (GpMemoryBitmap*)newBmp;
|
|
|
|
// Loop through all the property items, get it from current image and
|
|
// set it to the new image. Filter out and adjust some if necessary
|
|
|
|
for ( int i = 0; i < (int)uiNumOfProperty; ++i )
|
|
{
|
|
// Get size for the i th property item
|
|
|
|
status = GetPropertyItemSize(pList[i], &uiItemSize);
|
|
if ( status != Ok )
|
|
{
|
|
WARNING(("COWBitmap::RotateFlip-GetPropertyItemSize() failed"));
|
|
GpFree(pList);
|
|
return status;
|
|
}
|
|
|
|
// Allocate memory buffer for receiving it
|
|
|
|
pItem = (PropertyItem*)GpMalloc(uiItemSize);
|
|
if ( pItem == NULL )
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---GpMalloc() failed"));
|
|
GpFree(pList);
|
|
return OutOfMemory;
|
|
}
|
|
|
|
// Get the i th property item
|
|
|
|
status = GetPropertyItem(pList[i], uiItemSize, pItem);
|
|
if ( status != Ok )
|
|
{
|
|
WARNING(("COWriteBitmap::RotateFlip-GetPropertyItem() failed"));
|
|
GpFree(pItem);
|
|
GpFree(pList);
|
|
return status;
|
|
}
|
|
|
|
// We need to do some property information adjustment here according
|
|
// to the rfType
|
|
|
|
if ( (rfType == Rotate90FlipNone)
|
|
||(rfType == Rotate270FlipNone)
|
|
||(rfType == Rotate90FlipX)
|
|
||(rfType == Rotate270FlipX) )
|
|
{
|
|
// Swap the X and Y dimension info if rotate 90 or 270
|
|
|
|
switch ( pList[i] )
|
|
{
|
|
case PropertyTagImageWidth:
|
|
pItem->id = PropertyTagImageHeight;
|
|
break;
|
|
|
|
case PropertyTagImageHeight:
|
|
pItem->id = PropertyTagImageWidth;
|
|
break;
|
|
|
|
case PropertyTagXResolution:
|
|
pItem->id = PropertyTagYResolution;
|
|
break;
|
|
|
|
case PropertyTagYResolution:
|
|
pItem->id = PropertyTagXResolution;
|
|
break;
|
|
|
|
case PropertyTagResolutionXUnit:
|
|
pItem->id = PropertyTagResolutionYUnit;
|
|
break;
|
|
|
|
case PropertyTagResolutionYUnit:
|
|
pItem->id = PropertyTagResolutionXUnit;
|
|
break;
|
|
|
|
case PropertyTagResolutionXLengthUnit:
|
|
pItem->id = PropertyTagResolutionYLengthUnit;
|
|
break;
|
|
|
|
case PropertyTagResolutionYLengthUnit:
|
|
pItem->id = PropertyTagResolutionXLengthUnit;
|
|
break;
|
|
|
|
case PropertyTagExifPixXDim:
|
|
pItem->id = PropertyTagExifPixYDim;
|
|
break;
|
|
|
|
case PropertyTagExifPixYDim:
|
|
pItem->id = PropertyTagExifPixXDim;
|
|
break;
|
|
|
|
default:
|
|
// For rest of property IDs, no need to swap
|
|
|
|
break;
|
|
}
|
|
}// Case of rotate 90 degree
|
|
|
|
// Set the property item in the new GpMemoryBitmap object
|
|
|
|
hResult = pTempBmp->SetPropertyItem(*pItem);
|
|
if ( hResult != S_OK )
|
|
{
|
|
WARNING(("COWriteBitmap::RotateFlip-SetPropertyItem() failed"));
|
|
GpFree(pItem);
|
|
GpFree(pList);
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}
|
|
|
|
GpFree(pItem);
|
|
pItem = NULL;
|
|
}// Loop through all the property items
|
|
|
|
GpFree(pList);
|
|
}// if ( uiNumOfProperty > 0 )
|
|
|
|
// Replace the image
|
|
|
|
Bmp->Release();
|
|
Bmp = (GpMemoryBitmap*)newBmp;
|
|
State = MemBitmap;
|
|
|
|
// Set special hack for JPEG image
|
|
|
|
if (Img && (SpecialJPEGSave == TRUE))
|
|
{
|
|
Bmp->SetSpecialJPEG(Img);
|
|
}
|
|
|
|
SetDirtyFlag(TRUE);
|
|
|
|
// Since this image is dirty now, we don't need to have any connection
|
|
// with the original image if there is one
|
|
|
|
GpFree(Filename);
|
|
Filename = NULL;
|
|
|
|
if ( NULL != Stream )
|
|
{
|
|
Stream->Release();
|
|
Stream = NULL;
|
|
}
|
|
|
|
// We can't release the Img pointer until save() is called if this is a
|
|
// special JPEG lossless transform save case
|
|
|
|
if (Img && (SpecialJPEGSave == FALSE))
|
|
{
|
|
Img->Release();
|
|
Img = NULL;
|
|
}
|
|
|
|
// Update image info
|
|
|
|
hResult = Bmp->GetImageInfo(&SrcImageInfo);
|
|
|
|
if ( SUCCEEDED(hResult) )
|
|
{
|
|
PixelFormatInMem = SrcImageInfo.PixelFormat;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("CopyOnWriteBitmap::RotateFlip---GetImageInfo() failed"));
|
|
return MapHRESULTToGpStatus(hResult);
|
|
}
|
|
|
|
return Ok;
|
|
}// RotateFlip()
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
GpBitmap::GpBitmap(
|
|
const CopyOnWriteBitmap * internalBitmap
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
ASSERT((internalBitmap != NULL) && internalBitmap->IsValid());
|
|
InternalBitmap = (CopyOnWriteBitmap *)internalBitmap;
|
|
InternalBitmap->AddRef();
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
BOOL createInternalBitmap
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
if (createInternalBitmap)
|
|
{
|
|
// this case is used by the object factory for metafile playback
|
|
InternalBitmap = new CopyOnWriteBitmap();
|
|
}
|
|
else
|
|
{
|
|
InternalBitmap = NULL;
|
|
}
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
const GpBitmap * bitmap
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
ASSERT ((bitmap != NULL) && (bitmap->InternalBitmap != NULL) && bitmap->InternalBitmap->IsValid());
|
|
InternalBitmap = (CopyOnWriteBitmap *)bitmap->InternalBitmap;
|
|
InternalBitmap->AddRef();
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
// Destructor
|
|
// We don't want apps to use delete operator directly.
|
|
// Instead, they should use the Dispose method.
|
|
|
|
GpBitmap::~GpBitmap()
|
|
{
|
|
if (InternalBitmap != NULL)
|
|
{
|
|
InternalBitmap->Release();
|
|
InternalBitmap = NULL;
|
|
}
|
|
ScanBitmap.FreeData();
|
|
}
|
|
|
|
CopyOnWriteBitmap *
|
|
GpBitmap::LockForWrite()
|
|
{
|
|
ASSERT(InternalBitmap != NULL);
|
|
|
|
CopyOnWriteBitmap * writeableBitmap;
|
|
|
|
writeableBitmap = (CopyOnWriteBitmap *)InternalBitmap->LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
InternalBitmap = writeableBitmap;
|
|
UpdateUid();
|
|
return writeableBitmap;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
GpBitmap::Unlock() const
|
|
{
|
|
ASSERT(InternalBitmap != NULL);
|
|
|
|
BOOL valid = InternalBitmap->IsValid();
|
|
|
|
InternalBitmap->Unlock();
|
|
|
|
// If the operation we did on the internal bitmap somehow invalidated
|
|
// it then invalidate this GpBitmap object as well.
|
|
if (!valid)
|
|
{
|
|
InternalBitmap->Release();
|
|
((GpBitmap *)this)->InternalBitmap = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
GpBitmap::LockForRead() const
|
|
{
|
|
ASSERT(InternalBitmap != NULL);
|
|
|
|
InternalBitmap->LockForRead();
|
|
}
|
|
|
|
// Constructors
|
|
|
|
GpBitmap::GpBitmap(
|
|
const WCHAR* filename
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(filename);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
IStream* stream
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(stream);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
INT width,
|
|
INT height,
|
|
PixelFormatID format
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(width, height, format);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
INT width,
|
|
INT height,
|
|
PixelFormatID format,
|
|
GpGraphics * graphics
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(width, height, format, graphics);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
INT width,
|
|
INT height,
|
|
INT stride, // negative for bottom-up bitmaps
|
|
PixelFormatID format,
|
|
BYTE * scan0
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(width, height, stride, format, scan0);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
BITMAPINFO* gdiBitmapInfo,
|
|
VOID* gdiBitmapData,
|
|
BOOL ownBitmapData
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(gdiBitmapInfo, gdiBitmapData, ownBitmapData);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpBitmap::GpBitmap(
|
|
IDirectDrawSurface7 * surface
|
|
) : GpImage(ImageTypeBitmap), ScanBitmapRef(1)
|
|
{
|
|
InternalBitmap = CopyOnWriteBitmap::Create(surface);
|
|
ASSERT((InternalBitmap == NULL) || InternalBitmap->IsValid());
|
|
ScanBitmap.SetBitmap(this);
|
|
}
|
|
|
|
GpImage*
|
|
GpBitmap::Clone() const
|
|
{
|
|
return new GpBitmap(this);
|
|
}
|
|
|
|
GpBitmap*
|
|
GpBitmap::Clone(
|
|
const GpRect* rect,
|
|
PixelFormatID format
|
|
) const
|
|
{
|
|
BOOL isFullRect;
|
|
|
|
isFullRect = ((rect == NULL) ||
|
|
((rect->X == 0) && (rect->Y == 0) &&
|
|
(rect->Width == (INT)InternalBitmap->SrcImageInfo.Width) &&
|
|
(rect->Height == (INT)InternalBitmap->SrcImageInfo.Height)));
|
|
|
|
// If rect is full size and format is same,
|
|
// don't have to clone InternalBitmap.
|
|
if (isFullRect &&
|
|
((format == PixelFormatDontCare) ||
|
|
(format == InternalBitmap->SrcImageInfo.PixelFormat)))
|
|
{
|
|
return (GpBitmap *)this->Clone();
|
|
}
|
|
|
|
// else we have to do a clone of the internal bitmap
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
LockForRead();
|
|
if (isFullRect)
|
|
{
|
|
// It's faster to do the clone followed by the convert than
|
|
// to do the convert as part of the clone.
|
|
newBitmap->InternalBitmap = (CopyOnWriteBitmap *)InternalBitmap->Clone();
|
|
if (newBitmap->InternalBitmap != NULL)
|
|
{
|
|
if (newBitmap->InternalBitmap->ConvertFormat(format, NULL, NULL) != Ok)
|
|
{
|
|
newBitmap->InternalBitmap->Release();
|
|
newBitmap->InternalBitmap = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newBitmap->InternalBitmap = InternalBitmap->Clone(rect, format);
|
|
}
|
|
Unlock();
|
|
if (newBitmap->InternalBitmap == NULL)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(newBitmap->InternalBitmap->IsValid());
|
|
}
|
|
}
|
|
return newBitmap;
|
|
}
|
|
|
|
GpImage*
|
|
GpBitmap::CloneColorAdjusted(
|
|
GpRecolor * recolor,
|
|
ColorAdjustType type
|
|
) const
|
|
{
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
LockForRead();
|
|
newBitmap->InternalBitmap = InternalBitmap->CloneColorAdjusted(recolor, type);
|
|
Unlock();
|
|
if (newBitmap->InternalBitmap == NULL)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(newBitmap->InternalBitmap->IsValid());
|
|
}
|
|
}
|
|
return newBitmap;
|
|
}
|
|
|
|
// Similar to CloneColorAdjusted
|
|
GpStatus
|
|
GpBitmap::Recolor(
|
|
GpRecolor * recolor,
|
|
GpBitmap ** dstBitmap,
|
|
DrawImageAbort callback,
|
|
VOID * callbackData,
|
|
GpRect * rect
|
|
)
|
|
{
|
|
GpStatus status = GenericError;
|
|
|
|
if (dstBitmap == NULL)
|
|
{
|
|
// recolor this object -- need write lock
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
status = writeableBitmap->Recolor(recolor, NULL, callback, callbackData, rect);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
}
|
|
}
|
|
else // recolor into dstBitmap
|
|
{
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
LockForRead();
|
|
status = InternalBitmap->Recolor(recolor, &newBitmap->InternalBitmap, callback, callbackData, rect);
|
|
Unlock();
|
|
|
|
if (status != Ok)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((newBitmap->InternalBitmap != NULL) && (newBitmap->InternalBitmap->IsValid()));
|
|
}
|
|
}
|
|
*dstBitmap = newBitmap;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetEncoderParameterListSize(
|
|
IN CLSID* clsidEncoder,
|
|
OUT UINT* size
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetEncoderParameterListSize(clsidEncoder, size);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetEncoderParameterList(
|
|
IN CLSID* clsidEncoder,
|
|
IN UINT size,
|
|
OUT EncoderParameters* pBuffer
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetEncoderParameterList(clsidEncoder, size, pBuffer);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SaveToStream(
|
|
IStream* stream,
|
|
CLSID* clsidEncoder,
|
|
EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->SaveToStream(stream, clsidEncoder, encoderParams);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SaveToFile(
|
|
const WCHAR* filename,
|
|
CLSID* clsidEncoder,
|
|
EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->SaveToFile(filename, clsidEncoder, encoderParams);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SaveAdd(
|
|
const EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->SaveAdd(encoderParams);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SaveAdd(
|
|
GpImage* newBits,
|
|
const EncoderParameters* encoderParams
|
|
)
|
|
{
|
|
ASSERT(newBits != NULL);
|
|
|
|
GpStatus status = InvalidParameter;
|
|
|
|
if (newBits->GetImageType() == ImageTypeBitmap)
|
|
{
|
|
LockForRead();
|
|
status = InternalBitmap->SaveAdd(((GpBitmap *)newBits)->InternalBitmap, encoderParams);
|
|
Unlock();
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Dispose the bitmap object
|
|
|
|
VOID
|
|
GpBitmap::Dispose()
|
|
{
|
|
if (InterlockedDecrement(&ScanBitmapRef) <= 0)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
// Get bitmap information
|
|
|
|
GpStatus
|
|
GpBitmap::GetResolution(
|
|
REAL* xdpi,
|
|
REAL* ydpi
|
|
) const
|
|
{
|
|
GpStatus status = Ok;
|
|
LockForRead();
|
|
*xdpi = (REAL)InternalBitmap->SrcImageInfo.Xdpi;
|
|
*ydpi = (REAL)InternalBitmap->SrcImageInfo.Ydpi;
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPhysicalDimension(
|
|
REAL* width,
|
|
REAL* height
|
|
) const
|
|
{
|
|
GpStatus status = Ok;
|
|
LockForRead();
|
|
*width = (REAL)InternalBitmap->SrcImageInfo.Width;
|
|
*height = (REAL)InternalBitmap->SrcImageInfo.Height;
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetBounds(
|
|
GpRectF* rect,
|
|
GpPageUnit* unit
|
|
) const
|
|
{
|
|
GpStatus status = Ok;
|
|
LockForRead();
|
|
rect->X = rect->Y = 0;
|
|
rect->Width = (REAL) InternalBitmap->SrcImageInfo.Width;
|
|
rect->Height = (REAL) InternalBitmap->SrcImageInfo.Height;
|
|
*unit = UnitPixel;
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetSize(
|
|
Size* size
|
|
) const
|
|
{
|
|
GpStatus status = Ok;
|
|
LockForRead();
|
|
size->Width = InternalBitmap->SrcImageInfo.Width;
|
|
size->Height = InternalBitmap->SrcImageInfo.Height;
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetImageInfo(
|
|
ImageInfo * imageInfo
|
|
) const
|
|
{
|
|
if (NULL == imageInfo)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
GpStatus status = Ok;
|
|
LockForRead();
|
|
InternalBitmap->GetImageInfo(imageInfo);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpImage*
|
|
GpBitmap::GetThumbnail(
|
|
UINT thumbWidth,
|
|
UINT thumbHeight,
|
|
GetThumbnailImageAbort callback,
|
|
VOID * callbackData
|
|
)
|
|
{
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
LockForRead();
|
|
newBitmap->InternalBitmap = InternalBitmap->GetThumbnail(thumbWidth, thumbHeight, callback, callbackData);
|
|
Unlock();
|
|
if (newBitmap->InternalBitmap == NULL)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(newBitmap->InternalBitmap->IsValid());
|
|
}
|
|
}
|
|
return newBitmap;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetFrameCount(
|
|
const GUID* dimensionID,
|
|
UINT* count
|
|
) const
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetFrameCount(dimensionID, count);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetFrameDimensionsCount(
|
|
OUT UINT* count
|
|
) const
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetFrameDimensionsCount(count);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetFrameDimensionsList(
|
|
OUT GUID* dimensionIDs,
|
|
IN UINT count
|
|
) const
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetFrameDimensionsList(dimensionIDs, count);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SelectActiveFrame(
|
|
const GUID* dimensionID,
|
|
UINT frameIndex
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SelectActiveFrame(dimensionID, frameIndex);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPalette(
|
|
ColorPalette * palette,
|
|
INT size
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPalette(palette, size);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetPalette(
|
|
ColorPalette * palette
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SetPalette(palette);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
INT
|
|
GpBitmap::GetPaletteSize()
|
|
{
|
|
INT size;
|
|
LockForRead();
|
|
size = InternalBitmap->GetPaletteSize();
|
|
Unlock();
|
|
return size;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetTransparencyHint(
|
|
DpTransparency* transparency
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetTransparencyHint(transparency);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetTransparencyHint(
|
|
DpTransparency transparency
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->SetTransparencyHint(transparency);
|
|
Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetTransparencyFlags(
|
|
DpTransparency* transparency,
|
|
PixelFormatID loadFormat,
|
|
BYTE* minAlpha,
|
|
BYTE* maxAlpha
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetTransparencyFlags(transparency, loadFormat, minAlpha, maxAlpha);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
// Property related functions
|
|
|
|
GpStatus
|
|
GpBitmap::GetPropertyCount(
|
|
UINT* numOfProperty
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPropertyCount(numOfProperty);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPropertyIdList(
|
|
UINT numOfProperty,
|
|
PROPID* list
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPropertyIdList(numOfProperty, list);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPropertyItemSize(
|
|
PROPID propId,
|
|
UINT* size
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPropertyItemSize(propId, size);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPropertyItem(
|
|
PROPID propId,
|
|
UINT propSize,
|
|
PropertyItem* buffer
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPropertyItem(propId, propSize, buffer);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPropertySize(
|
|
UINT* totalBufferSize,
|
|
UINT* numProperties
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPropertySize(totalBufferSize, numProperties);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetAllPropertyItems(
|
|
UINT totalBufferSize,
|
|
UINT numProperties,
|
|
PropertyItem* allItems
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetAllPropertyItems(totalBufferSize, numProperties, allItems);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::RemovePropertyItem(
|
|
PROPID propId
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->RemovePropertyItem(propId);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetPropertyItem(
|
|
PropertyItem* item
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SetPropertyItem(item);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
// Retrieve bitmap data
|
|
|
|
GpStatus
|
|
GpBitmap::LockBits(
|
|
const GpRect* rect,
|
|
UINT flags,
|
|
PixelFormatID pixelFormat,
|
|
BitmapData* bmpdata,
|
|
INT width,
|
|
INT height
|
|
) const
|
|
{
|
|
ASSERT(InternalBitmap != NULL);
|
|
|
|
if (flags & ImageLockModeWrite)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = ((GpBitmap *)this)->LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->LockBits(rect, flags, pixelFormat, bmpdata, width, height);
|
|
writeableBitmap->Unlock();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
else
|
|
{
|
|
// Lock For read case
|
|
// First we need to check if this is the 1st LockForRead on this image
|
|
// object or not.
|
|
|
|
if ( InternalBitmap->ObjRefCount > 1 )
|
|
{
|
|
// We have more than one LockForRead on this object
|
|
// Note: this part needs to be re-visited in V2. We have a big
|
|
// problem here not allowing user to do more than once for LockBits
|
|
// for read. So we need to make a copy even though theory says
|
|
// that we should not have to.
|
|
|
|
CopyOnWriteBitmap * writeableBitmap = ((GpBitmap *)this)->LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->LockBits(rect, flags, pixelFormat, bmpdata, width, height);
|
|
writeableBitmap->Unlock();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
else
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->LockBits(rect, flags, pixelFormat, bmpdata, width, height);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::UnlockBits(
|
|
BitmapData* bmpdata,
|
|
BOOL Destroy
|
|
) const
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->UnlockBits(bmpdata, Destroy);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
// Get and set pixel on the bitmap.
|
|
GpStatus
|
|
GpBitmap::GetPixel(
|
|
INT x,
|
|
INT y,
|
|
ARGB * color
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPixel(x, y, color);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetPixel(
|
|
INT x,
|
|
INT y,
|
|
ARGB color
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SetPixel(x, y, color);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::RotateFlip(
|
|
RotateFlipType rfType
|
|
)
|
|
{
|
|
CopyOnWriteBitmap* pWriteableBitmap = LockForWrite();
|
|
|
|
if ( pWriteableBitmap != NULL )
|
|
{
|
|
GpStatus status = pWriteableBitmap->RotateFlip(rfType);
|
|
|
|
pWriteableBitmap->Unlock();
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
|
|
return GenericError;
|
|
}// RotateFlip()
|
|
|
|
BOOL
|
|
GpBitmap::IsDirty() const
|
|
{
|
|
LockForRead();
|
|
BOOL dirty = InternalBitmap->IsDirty();
|
|
Unlock();
|
|
return dirty;
|
|
}
|
|
|
|
// Derive a graphics context on top of the bitmap object
|
|
|
|
GpGraphics*
|
|
GpBitmap::GetGraphicsContext()
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpGraphics * g = NULL;
|
|
|
|
// NTRAID#NTBUG9-368452-2001-04-13-gilmanw "ISSUE: allow only one GpGraphics per bitmap"
|
|
//
|
|
// Currently create a new GpGraphics each time GetGraphicsContext
|
|
// is called. Perhaps should cache the GpGraphics and return that
|
|
// to all callers. Otherwise, there may be synchronization issues
|
|
// if there are multiple GpGraphics per surface.
|
|
|
|
if (writeableBitmap->State == MemBitmap && writeableBitmap->Bmp != NULL &&
|
|
writeableBitmap->Bmp->creationFlag == GpMemoryBitmap::CREATEDFROM_DDRAWSURFACE)
|
|
{
|
|
// NTRAID#NTBUG9-368458-2001-04-13-gilmanw "ISSUE: lose association with Image for DDraw surfs"
|
|
//
|
|
// The Image as well as the graphics are only wrappers around the
|
|
// direct draw surface. When we create the GpGraphics in this
|
|
// way we lose all association with the Image (CopyOnWriteBitmap)
|
|
// object. This may not be the right behavior.
|
|
|
|
g = GpGraphics::GetFromDirectDrawSurface(writeableBitmap->Bmp->ddrawSurface);
|
|
}
|
|
else
|
|
{
|
|
ImageInfo imageInfo;
|
|
writeableBitmap->GetImageInfo(&imageInfo);
|
|
|
|
// since GpGraphics will end up pointing to ScanBitmap structure
|
|
// we need to make sure bitmap won't be deleted while
|
|
// there is a graphics wrapped around it
|
|
|
|
IncScanBitmapRef();
|
|
g = GpGraphics::GetFromGdipBitmap(this, &imageInfo, &ScanBitmap, writeableBitmap->Display);
|
|
if (!CheckValid(g))
|
|
{
|
|
DecScanBitmapRef();
|
|
}
|
|
}
|
|
|
|
writeableBitmap->Unlock();
|
|
return g;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::InitializeSurfaceForGdipBitmap(
|
|
DpBitmap * surface,
|
|
INT width,
|
|
INT height
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
// Currently this is only called when preparing a surface as a source
|
|
// surface, not as a dest surface, so we only need a read lock.
|
|
LockForRead();
|
|
ImageInfo imageInfo;
|
|
InternalBitmap->GetImageInfo(&imageInfo);
|
|
surface->InitializeForGdipBitmap(width, height, &imageInfo, &ScanBitmap, InternalBitmap->Display);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
// Derive an HDC for interop on top of the bitmap object
|
|
|
|
HDC
|
|
GpBitmap::GetHdc()
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
HDC hdc;
|
|
hdc = writeableBitmap->GetHdc();
|
|
writeableBitmap->Unlock();
|
|
return hdc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
GpBitmap::ReleaseHdc(HDC hdc)
|
|
{
|
|
LockForRead();
|
|
InternalBitmap->ReleaseHdc(hdc);
|
|
Unlock();
|
|
return;
|
|
}
|
|
|
|
// Serialization
|
|
|
|
UINT
|
|
GpBitmap::GetDataSize() const
|
|
{
|
|
UINT dataSize;
|
|
LockForRead();
|
|
dataSize = InternalBitmap->GetDataSize();
|
|
Unlock();
|
|
return dataSize;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetData(
|
|
IStream * stream
|
|
) const
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetData(stream);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetData(
|
|
const BYTE * dataBuffer,
|
|
UINT size
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SetData(dataBuffer, size);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetCompressedData(
|
|
DpCompressedData * compressed_data,
|
|
BOOL getJPEG,
|
|
BOOL getPNG,
|
|
HDC hdc
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetCompressedData(compressed_data, getJPEG, getPNG, hdc);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::DeleteCompressedData(
|
|
DpCompressedData * compressed_data
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->DeleteCompressedData(compressed_data);
|
|
Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
|
|
// Color adjust
|
|
|
|
GpStatus
|
|
GpBitmap::ColorAdjust(
|
|
GpRecolor * recolor,
|
|
ColorAdjustType type
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->ColorAdjust(recolor, type);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::ColorAdjust(
|
|
GpRecolor * recolor,
|
|
PixelFormatID pixfmt,
|
|
DrawImageAbort callback,
|
|
VOID * callbackData
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->ColorAdjust(recolor, pixfmt, callback, callbackData);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::GetPixelFormatID(
|
|
PixelFormatID* pixfmt
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->GetPixelFormatID(pixfmt);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
INT
|
|
GpBitmap::GetDecodeState()
|
|
{
|
|
INT decodeState;
|
|
LockForRead();
|
|
decodeState = InternalBitmap->State;
|
|
Unlock();
|
|
return decodeState;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::ForceValidation()
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->LoadIntoMemory(PixelFormatDontCare, NULL, NULL);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::SetResolution(
|
|
REAL xdpi,
|
|
REAL ydpi
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
GpStatus status;
|
|
status = writeableBitmap->SetResolution(xdpi, ydpi);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
return status;
|
|
}
|
|
return GenericError;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::PreDraw(
|
|
INT numPoints,
|
|
GpPointF * dstPoints,
|
|
GpRectF * srcRect,
|
|
INT numBitsPerPixel
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->PreDraw(numPoints, dstPoints, srcRect, numBitsPerPixel);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
// Interop:
|
|
|
|
GpStatus
|
|
GpBitmap::CreateFromHBITMAP(
|
|
HBITMAP hbm,
|
|
HPALETTE hpal,
|
|
GpBitmap** bitmap
|
|
)
|
|
{
|
|
ASSERT(bitmap != NULL);
|
|
GpStatus status = GenericError;
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
status = CopyOnWriteBitmap::CreateFromHBITMAP(hbm, hpal, &newBitmap->InternalBitmap);
|
|
|
|
if (status != Ok)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((newBitmap->InternalBitmap != NULL) && (newBitmap->InternalBitmap->IsValid()));
|
|
}
|
|
}
|
|
*bitmap = newBitmap;
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::CreateBitmapAndFillWithBrush(
|
|
InterpolationMode interpolationMode,
|
|
PixelOffsetMode pixelOffsetMode,
|
|
const GpMatrix * worldToDevice,
|
|
const GpRect * drawBounds,
|
|
GpBrush * brush,
|
|
GpBitmap ** bitmap,
|
|
PixelFormatID pixelFormat
|
|
)
|
|
{
|
|
ASSERT ((drawBounds->Width > 0) && (drawBounds->Height > 0));
|
|
ASSERT (bitmap != NULL);
|
|
|
|
GpStatus status = GenericError;
|
|
|
|
*bitmap = NULL;
|
|
|
|
// First, construct the correct brush transform to use when rendering
|
|
// into the bitmap. The brush transform is the concatenation of the
|
|
// current brush transform, the current worldToDevice transform, and
|
|
// a translation transform that maps from the drawBounds to the
|
|
// bitmap bounds.
|
|
|
|
GpMatrix saveBrushMatrix;
|
|
GpMatrix * deviceMatrix = const_cast<GpMatrix *>(&((brush->GetDeviceBrush())->Xform));
|
|
|
|
saveBrushMatrix = *deviceMatrix;
|
|
|
|
GpMatrix newBrushMatrix = saveBrushMatrix;
|
|
|
|
if (worldToDevice != NULL)
|
|
{
|
|
newBrushMatrix.Append(*worldToDevice);
|
|
}
|
|
|
|
newBrushMatrix.Translate(
|
|
(REAL)-(drawBounds->X),
|
|
(REAL)-(drawBounds->Y),
|
|
MatrixOrderAppend
|
|
);
|
|
|
|
|
|
BOOL restoreWrapMode = FALSE;
|
|
|
|
// When we're drawing a texture brush into a bitmap, if the texture is
|
|
// supposed to fill the bitmap, then don't use clamp mode, because clamp
|
|
// mode will end up bleeding alpha into the image along the right and
|
|
// bottom edges, which is undesirable -- especially for down-level bitmaps
|
|
// where we end up with what looks like a dotted line along the edges
|
|
// of the bitmap.
|
|
if ((brush->GetBrushType() == BrushTypeTextureFill) &&
|
|
(((GpTexture *)brush)->GetWrapMode() == WrapModeClamp) &&
|
|
(newBrushMatrix.IsTranslateScale()))
|
|
{
|
|
GpBitmap* brushBitmap = ((GpTexture *)brush)->GetBitmap();
|
|
if (brushBitmap != NULL)
|
|
{
|
|
Size size;
|
|
brushBitmap->GetSize(&size);
|
|
|
|
GpRectF transformedRect(0.0f, 0.0f, (REAL)size.Width, (REAL)size.Height);
|
|
newBrushMatrix.TransformRect(transformedRect);
|
|
|
|
// get the transformed width
|
|
INT deltaValue = abs(GpRound(transformedRect.Width) - drawBounds->Width);
|
|
|
|
// We might be off a little because of the pixel offset mode
|
|
// or a matrix that isn't quite right for whatever reason.
|
|
if (deltaValue <= 2)
|
|
{
|
|
// get the transformed height
|
|
deltaValue = abs(GpRound(transformedRect.Height) - drawBounds->Height);
|
|
|
|
if (deltaValue <= 2)
|
|
{
|
|
if ((abs(GpRound(transformedRect.X)) <= 2) &&
|
|
(abs(GpRound(transformedRect.Y)) <= 2))
|
|
{
|
|
((GpTexture *)brush)->SetWrapMode(WrapModeTileFlipXY);
|
|
restoreWrapMode = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newBrushMatrix.IsInvertible())
|
|
{
|
|
*deviceMatrix = newBrushMatrix;
|
|
|
|
GpBitmap * bitmapImage = new GpBitmap(drawBounds->Width, drawBounds->Height, pixelFormat);
|
|
|
|
if (bitmapImage != NULL)
|
|
{
|
|
if (bitmapImage->IsValid())
|
|
{
|
|
GpGraphics * graphics = bitmapImage->GetGraphicsContext();
|
|
|
|
if (graphics != NULL)
|
|
{
|
|
if (graphics->IsValid())
|
|
{
|
|
// we have to lock the graphics so the driver doesn't assert
|
|
GpLock lockGraphics(graphics->GetObjectLock());
|
|
|
|
ASSERT(lockGraphics.IsValid());
|
|
|
|
graphics->SetCompositingMode(CompositingModeSourceCopy);
|
|
graphics->SetInterpolationMode(interpolationMode);
|
|
graphics->SetPixelOffsetMode(pixelOffsetMode);
|
|
|
|
// now fill the bitmap image with the brush
|
|
GpRectF destRect(0.0f, 0.0f, (REAL)drawBounds->Width, (REAL)drawBounds->Height);
|
|
status = graphics->FillRects(brush, &destRect, 1);
|
|
}
|
|
else
|
|
{
|
|
WARNING(("graphics from bitmap image not valid"));
|
|
}
|
|
delete graphics;
|
|
}
|
|
else
|
|
{
|
|
WARNING(("could not create graphics from bitmap image"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("bitmap image is not valid"));
|
|
}
|
|
if (status == Ok)
|
|
{
|
|
*bitmap = bitmapImage;
|
|
}
|
|
else
|
|
{
|
|
bitmapImage->Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("could not create bitmap image"));
|
|
}
|
|
*deviceMatrix = saveBrushMatrix;
|
|
}
|
|
|
|
if (restoreWrapMode)
|
|
{
|
|
((GpTexture *)brush)->SetWrapMode(WrapModeClamp);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::DrawAndHalftoneForStretchBlt(
|
|
HDC hdc,
|
|
BITMAPINFO * bmpInfo,
|
|
BYTE * bits,
|
|
INT srcX,
|
|
INT srcY,
|
|
INT srcWidth,
|
|
INT srcHeight,
|
|
INT destWidth,
|
|
INT destHeight,
|
|
BITMAPINFO ** destBmpInfo,
|
|
BYTE ** destBmpBits,
|
|
HBITMAP * destDIBSection,
|
|
InterpolationMode interpolationMode
|
|
)
|
|
{
|
|
ASSERT(hdc != NULL && bmpInfo != NULL && bits != NULL &&
|
|
destBmpInfo != NULL && destBmpBits != NULL &&
|
|
destDIBSection != NULL);
|
|
ASSERT(destWidth > 0 && destHeight > 0);
|
|
|
|
GpStatus status = GenericError;
|
|
|
|
ASSERT(::GetDeviceCaps(hdc, BITSPIXEL) == 8);
|
|
*destBmpInfo = (BITMAPINFO*) GpMalloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
|
|
|
|
if (*destBmpInfo == NULL)
|
|
{
|
|
return OutOfMemory;
|
|
}
|
|
|
|
BITMAPINFO *dst = *destBmpInfo;
|
|
GpMemset(dst, 0, sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
|
|
dst->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
dst->bmiHeader.biPlanes = 1;
|
|
dst->bmiHeader.biBitCount = 8;
|
|
dst->bmiHeader.biWidth = destWidth;
|
|
dst->bmiHeader.biHeight = destHeight;
|
|
|
|
// We need to create a Memory DC to select a DibSection into it and finally
|
|
// wrap a graphics around it.
|
|
|
|
HPALETTE currentPalette = (HPALETTE)::GetCurrentObject(hdc, OBJ_PAL);
|
|
WORD paletteEntries;
|
|
::GetObjectA(currentPalette, sizeof(WORD), (LPVOID)&paletteEntries);
|
|
::GetPaletteEntries(currentPalette, 0, paletteEntries, (LPPALETTEENTRY) &(dst->bmiColors));
|
|
dst->bmiHeader.biClrUsed = paletteEntries;
|
|
HDC memDC = ::CreateCompatibleDC(hdc);
|
|
*destDIBSection = ::CreateDIBSection(hdc, dst, DIB_RGB_COLORS,
|
|
(VOID**) destBmpBits, NULL, 0);
|
|
|
|
if (*destDIBSection != NULL && memDC != NULL)
|
|
{
|
|
::SelectObject(memDC, *destDIBSection);
|
|
::SelectPalette(memDC, currentPalette, FALSE);
|
|
::RealizePalette(memDC);
|
|
GpGraphics *g = GpGraphics::GetFromHdc(memDC);
|
|
if (g != NULL)
|
|
{
|
|
if(g->IsValid())
|
|
{
|
|
GpBitmap *src = new GpBitmap(bmpInfo, bits, FALSE);
|
|
if (src != NULL)
|
|
{
|
|
if( src->IsValid())
|
|
{
|
|
GpLock lock(g->GetObjectLock());
|
|
g->SetCompositingMode(CompositingModeSourceCopy);
|
|
g->SetInterpolationMode(interpolationMode);
|
|
g->SetPixelOffsetMode(PixelOffsetModeHalf);
|
|
status = g->DrawImage(src,
|
|
GpRectF(0.0f, 0.0f, (REAL)destWidth, (REAL)destHeight),
|
|
GpRectF((REAL)srcX, (REAL)srcY, (REAL)srcWidth, (REAL)srcHeight),
|
|
UnitPixel);
|
|
}
|
|
src->Dispose();
|
|
}
|
|
delete g;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (memDC != NULL)
|
|
{
|
|
::DeleteDC(memDC);
|
|
}
|
|
|
|
// If we failed delete our allocations
|
|
if (status != Ok)
|
|
{
|
|
GpFree(*destBmpInfo);
|
|
*destBmpInfo = NULL;
|
|
if (*destDIBSection != NULL)
|
|
{
|
|
::DeleteObject(*destDIBSection);
|
|
*destDIBSection = NULL;
|
|
}
|
|
*destBmpBits = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
GpStatus
|
|
GpBitmap::CreateHBITMAP(
|
|
HBITMAP * phbm,
|
|
ARGB background
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->CreateHBITMAP(phbm, background);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::ICMFrontEnd(
|
|
GpBitmap ** dstBitmap,
|
|
DrawImageAbort callback,
|
|
VOID * callbackData,
|
|
GpRect * rect
|
|
)
|
|
{
|
|
GpStatus status = GenericError;
|
|
|
|
if (dstBitmap == NULL)
|
|
{
|
|
// change this object -- need write lock
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
status = writeableBitmap->ICMFrontEnd(NULL, callback, callbackData, rect);
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
}
|
|
}
|
|
else // use dstBitmap
|
|
{
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
LockForRead();
|
|
status = InternalBitmap->ICMFrontEnd(&newBitmap->InternalBitmap, callback, callbackData, rect);
|
|
Unlock();
|
|
|
|
if (status != Ok)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((newBitmap->InternalBitmap != NULL) && (newBitmap->InternalBitmap->IsValid()));
|
|
}
|
|
}
|
|
*dstBitmap = newBitmap;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::CreateFromHICON(
|
|
HICON hicon,
|
|
GpBitmap** bitmap
|
|
)
|
|
{
|
|
ASSERT(bitmap != NULL);
|
|
GpStatus status = GenericError;
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
status = CopyOnWriteBitmap::CreateFromHICON(hicon, &newBitmap->InternalBitmap);
|
|
|
|
if (status != Ok)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((newBitmap->InternalBitmap != NULL) && (newBitmap->InternalBitmap->IsValid()));
|
|
}
|
|
}
|
|
*bitmap = newBitmap;
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::CreateHICON(
|
|
HICON * phicon
|
|
)
|
|
{
|
|
GpStatus status;
|
|
LockForRead();
|
|
status = InternalBitmap->CreateHICON(phicon);
|
|
Unlock();
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpBitmap::CreateFromResource(
|
|
HINSTANCE hInstance,
|
|
LPWSTR lpBitmapName,
|
|
GpBitmap** bitmap
|
|
)
|
|
{
|
|
ASSERT(bitmap != NULL);
|
|
GpStatus status = GenericError;
|
|
GpBitmap * newBitmap = new GpBitmap(FALSE);
|
|
|
|
if (newBitmap != NULL)
|
|
{
|
|
status = CopyOnWriteBitmap::CreateFromResource(hInstance, lpBitmapName, &newBitmap->InternalBitmap);
|
|
|
|
if (status != Ok)
|
|
{
|
|
delete newBitmap;
|
|
newBitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((newBitmap->InternalBitmap != NULL) && (newBitmap->InternalBitmap->IsValid()));
|
|
}
|
|
}
|
|
*bitmap = newBitmap;
|
|
return status;
|
|
}
|
|
|
|
// We need to know if the bitmap is associated with a display
|
|
// so we know how to handle the page transform when it is
|
|
// set to UnitDisplay.
|
|
BOOL
|
|
GpBitmap::IsDisplay() const
|
|
{
|
|
BOOL isDisplay;
|
|
LockForRead();
|
|
isDisplay = InternalBitmap->Display;
|
|
Unlock();
|
|
return isDisplay;
|
|
}
|
|
|
|
VOID
|
|
GpBitmap::SetDisplay(
|
|
BOOL display
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
writeableBitmap->Display = display;
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
}
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
GpBitmap::IsICMConvert() const
|
|
{
|
|
BOOL isICMConvert;
|
|
LockForRead();
|
|
isICMConvert = InternalBitmap->ICMConvert;
|
|
Unlock();
|
|
return isICMConvert;
|
|
}
|
|
|
|
VOID
|
|
GpBitmap::SetICMConvert(
|
|
BOOL icm
|
|
)
|
|
{
|
|
CopyOnWriteBitmap * writeableBitmap = LockForWrite();
|
|
|
|
if (writeableBitmap != NULL)
|
|
{
|
|
writeableBitmap->ICMConvert = icm;
|
|
writeableBitmap->Unlock();
|
|
UpdateUid();
|
|
}
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
GpBitmap::IsValid() const
|
|
{
|
|
// If the bitmap came from a different version of GDI+, its tag
|
|
// will not match, and it won't be considered valid.
|
|
return ((InternalBitmap != NULL) && InternalBitmap->IsValid()
|
|
&& GpImage::IsValid());
|
|
}
|
|
|
|
GpStatus
|
|
ConvertTo16BppAndFlip(
|
|
GpBitmap * sourceBitmap,
|
|
GpBitmap * & destBitmap
|
|
)
|
|
{
|
|
ASSERT ((sourceBitmap != NULL) && sourceBitmap->IsValid());
|
|
|
|
GpStatus status = GenericError;
|
|
Size size;
|
|
|
|
sourceBitmap->GetSize(&size);
|
|
|
|
destBitmap = new GpBitmap(size.Width, size.Height, PixelFormat16bppRGB555);
|
|
if ((destBitmap != NULL) && destBitmap->IsValid())
|
|
{
|
|
// We have to draw it with a graphics, because if we just
|
|
// clone it, then the format converter is used which doesn't
|
|
// do dithering.
|
|
GpGraphics * g = destBitmap->GetGraphicsContext();
|
|
|
|
if (g != NULL)
|
|
{
|
|
if (g->IsValid())
|
|
{
|
|
// we have to lock the graphics so the driver doesn't assert
|
|
GpLock lockGraphics(g->GetObjectLock());
|
|
|
|
ASSERT(lockGraphics.IsValid());
|
|
|
|
// flip it upside down like GDI wants it
|
|
GpRectF realDestRect(0.0f, (REAL)size.Height, (REAL)size.Width, (REAL)(-size.Height));
|
|
g->SetCompositingMode(CompositingModeSourceCopy);
|
|
g->SetInterpolationMode(InterpolationModeNearestNeighbor);
|
|
g->SetPixelOffsetMode(PixelOffsetModeHalf);
|
|
status = g->DrawImage(sourceBitmap, realDestRect);
|
|
}
|
|
delete g;
|
|
}
|
|
}
|
|
if ((status != Ok) && (destBitmap != NULL))
|
|
{
|
|
destBitmap->Dispose();
|
|
destBitmap = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|