/**************************************************************************\ * * 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; cntYHeight; 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 (&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 (&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 (static_cast(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 (&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(dst->Scan0); BYTE* maskScan = static_cast(mask->Scan0); for (UINT row = 0; row < dst->Height; row++) { ARGB *dstPixel = static_cast(static_cast(dstScan)); ARGB *maskPixel = static_cast(static_cast(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(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(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(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 (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(imageInfo.Width * 4)); } ASSERT(InteropData.Hdc != NULL); // Fill the bitmap with a sentinal pattern. { INT count = InteropData.Width * InteropData.Height; UINT32 *bits = static_cast(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(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(InteropData.Bits); GpStatus status = Ok; for (curRow = 0; (curRow < InteropData.Height) && (status == Ok); curRow++) { BOOL rowLocked = FALSE; BitmapData bitmapData; ARGB *interopPixel = static_cast(static_cast(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(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(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(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(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(src->Scan0); BYTE* maskScan = static_cast(mask->Scan0); for (UINT row = 0; row < src->Height; row++) { ARGB *srcPixel = static_cast(static_cast(srcScan)); ARGB *maskPixel = static_cast(static_cast(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 (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 (GetDistance(dstPoints[0], dstPoints[1]) + 0.5); value[1] = static_cast (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(Bmp->Scan0); ARGB *scanPtr; for (y = 0; y < Bmp->Height; y++) { scanPtr = reinterpret_cast(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(&((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; }