/**************************************************************************\ * * Copyright (c) 1999 Microsoft Corporation * * Module Name: * * MetaWmf.cpp * * Abstract: * * Methods for playing and recoloring a WMF. * * Created: * * 12/13/1999 DCurtis * \**************************************************************************/ #include "Precomp.hpp" #include "MetaWmf.hpp" #ifndef BI_CMYK // from wingdip.h #define BI_CMYK 10L #define BI_CMYKRLE8 11L #define BI_CMYKRLE4 12L #endif inline static BOOL IsDwordAligned( VOID * pointer ) { return (((ULONG_PTR)pointer & (sizeof(DWORD) - 1)) == 0); } inline static BOOL IsPostscriptPrinter( HDC hdc ) { // It is a PostScript printer if POSTSCRIPT_PASSTHROUGH or // POSTSCRIPT_IGNORE is available int iWant1 = POSTSCRIPT_PASSTHROUGH; int iWant2 = POSTSCRIPT_IGNORE; return ((Escape(hdc, QUERYESCSUPPORT, sizeof(iWant1), (LPCSTR)&iWant1, NULL) != 0) || (Escape(hdc, QUERYESCSUPPORT, sizeof(iWant2), (LPCSTR)&iWant2, NULL) != 0)); } // Some escapes apparently cause NT 3.51 to crash, so skip them inline static BOOL SkipEscape( INT escapeCode ) { switch (escapeCode) { case GETPHYSPAGESIZE: // 12 case GETPRINTINGOFFSET: // 13 case GETSCALINGFACTOR: // 14 case BEGIN_PATH: // 4096 case CLIP_TO_PATH: // 4097 case END_PATH: // 4098 return TRUE; default: return FALSE; } } inline static BOOL IsOfficeArtData( UINT recordSize, const WORD * recordData ) { return (recordData[0] == MFCOMMENT) && (recordSize > 16) && ((INT)recordSize >= (recordData[1] + 10)) && (GpMemcmp(recordData + 2, "TNPPOA", 6) == 0); } // The structure which defines the contents of a comment for a WMF or PICT, // for an EMF use GdiComment() and the "approved" format (see the Win32 // documentation) - this basically is the same except that it has a 4 byte // kind field. For a PICT this is the format of an ApplicationComment (kind // 100). #pragma pack(push, GDIP_pack, 2) typedef struct { ULONG Signature; // Identifes the comment writer. USHORT Kind; // Type of comment (writer specific) // Comment data follows here. } WmfComment; typedef struct { WORD lbStyle; COLORREF lbColor; SHORT lbHatch; } LOGBRUSH16; typedef struct { WORD lopnStyle; POINTS lopnWidth; COLORREF lopnColor; } LOGPEN16; typedef struct { SHORT bmType; SHORT bmWidth; SHORT bmHeight; SHORT bmWidthBytes; BYTE bmPlanes; BYTE bmBitsPixel; LPBYTE bmBits; } BITMAP16; typedef struct tagLOGFONT16 { SHORT lfHeight; SHORT lfWidth; SHORT lfEscapement; SHORT lfOrientation; SHORT lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; BYTE lfFaceName[LF_FACESIZE]; } LOGFONT16; #pragma pack(pop, GDIP_pack) inline static const WmfComment UNALIGNED * GetWmfComment( const WORD * recordData, ULONG signature, UINT kind ) { // Assumes you've already checked that // (wmfCommentRecord->rdFunction == META_ESCAPE && // wmfCommentRecord->rdParm[0] == MFCOMMENT) const WmfComment UNALIGNED * wmfComment = (const WmfComment UNALIGNED *)&(recordData[2]); if ((wmfComment->Signature == signature) && (wmfComment->Kind == kind)) { return wmfComment; } return NULL; } inline static INT GetDibByteWidth( INT biWidth, INT biPlanes, INT biBitCount ) { return (((biWidth * biPlanes * biBitCount) + 31) & ~31) / 8; } inline static RGBQUAD UNALIGNED * GetDibColorTable( BITMAPINFOHEADER UNALIGNED * dibInfo ) { return (RGBQUAD UNALIGNED *)(((BYTE *)dibInfo) + dibInfo->biSize); } static BYTE * GetDibBits( BITMAPINFOHEADER UNALIGNED * dibInfo, UINT numPalEntries, UINT usage ) { ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER)); INT colorSize = 0; if (numPalEntries > 0) { if ((usage == DIB_PAL_COLORS) && (dibInfo->biCompression != BI_BITFIELDS) && (dibInfo->biCompression != BI_CMYK)) { // Make sure it is aligned colorSize = ((numPalEntries * sizeof(INT16)) + 3) & ~3; } else { colorSize = numPalEntries * sizeof(RGBQUAD); } } return ((BYTE *)GetDibColorTable(dibInfo)) + colorSize; } UINT GetDibBitsSize( BITMAPINFOHEADER UNALIGNED * dibInfo ) { // Check for PM-style DIB if (dibInfo->biSize >= sizeof(BITMAPINFOHEADER)) { // not a core header if (dibInfo->biWidth > 0) // can't handle negative width { if ((dibInfo->biCompression == BI_RGB) || (dibInfo->biCompression == BI_BITFIELDS) || (dibInfo->biCompression == BI_CMYK)) { INT posHeight = dibInfo->biHeight; if (posHeight < 0) { posHeight = -posHeight; } return posHeight * GetDibByteWidth(dibInfo->biWidth, dibInfo->biPlanes, dibInfo->biBitCount); } return dibInfo->biSizeImage; } WARNING(("0 or negative DIB width")); return 0; } else // it is a PM-style DIB { BITMAPCOREHEADER UNALIGNED * coreDibInfo = (BITMAPCOREHEADER UNALIGNED *)dibInfo; // width and height must be > 0 for COREINFO dibs if ((coreDibInfo->bcWidth > 0) && (coreDibInfo->bcHeight > 0)) { return coreDibInfo->bcHeight * GetDibByteWidth(coreDibInfo->bcWidth,coreDibInfo->bcPlanes, coreDibInfo->bcBitCount); } WARNING(("0 or negative DIB width or height")); return 0; } } BOOL GetDibNumPalEntries( BOOL isWmfDib, UINT biSize, UINT biBitCount, UINT biCompression, UINT biClrUsed, UINT * numPalEntries ) { UINT maxPalEntries = 0; // Dibs in a WMF always have the bitfields for 16 and 32-bpp dibs. if (((biBitCount == 16) || (biBitCount == 32)) && isWmfDib) { biCompression = BI_BITFIELDS; } switch (biCompression) { case BI_BITFIELDS: // // Handle 16 and 32 bit per pel bitmaps. // switch (biBitCount) { case 16: case 32: break; default: WARNING(("BI_BITFIELDS not Valid for this biBitCount")); return FALSE; } if (biSize <= sizeof(BITMAPINFOHEADER)) { biClrUsed = maxPalEntries = 3; } else { // // masks are part of BITMAPV4 and greater // biClrUsed = maxPalEntries = 0; } break; case BI_RGB: switch (biBitCount) { case 1: case 4: case 8: maxPalEntries = 1 << biBitCount; break; default: maxPalEntries = 0; switch (biBitCount) { case 16: case 24: case 32: break; default: WARNING(("Invalid biBitCount in BI_RGB")); return FALSE; } } break; case BI_CMYK: switch (biBitCount) { case 1: case 4: case 8: maxPalEntries = 1 << biBitCount; break; case 32: maxPalEntries = 0; break; default: WARNING(("Invalid biBitCount in BI_CMYK")); return FALSE; } break; case BI_RLE4: case BI_CMYKRLE4: if (biBitCount != 4) { WARNING(("Invalid biBitCount in BI_RLE4")); return FALSE; } maxPalEntries = 16; break; case BI_RLE8: case BI_CMYKRLE8: if (biBitCount != 8) { WARNING(("Invalid biBitCount in BI_RLE8")); return FALSE; } maxPalEntries = 256; break; case BI_JPEG: case BI_PNG: maxPalEntries = 0; break; default: WARNING(("Invalid biCompression")); return FALSE; } if (biClrUsed != 0) { if (biClrUsed <= maxPalEntries) { maxPalEntries = biClrUsed; } } *numPalEntries = maxPalEntries; return TRUE; } GdipHdcType GetHdcType( HDC hdc ) { GdipHdcType hdcType = UnknownHdc; UINT dcType = GetDCType(hdc); switch (dcType) { case OBJ_DC: { INT technology = GetDeviceCaps(hdc, TECHNOLOGY); if (technology == DT_RASDISPLAY) { hdcType = ScreenHdc; } else if (technology == DT_RASPRINTER) { if (IsPostscriptPrinter(hdc)) { hdcType = PostscriptPrinterHdc; } else { hdcType = PrinterHdc; } } else { WARNING(("Unknown HDC technology!")); } } break; case OBJ_MEMDC: hdcType = BitmapHdc; break; case OBJ_ENHMETADC: // When metafile spooling, the printer DC will be of type // OBJ_ENHMETADC on Win9x and NT4 (but not NT5 due to a fix // to NT bug 98810). We need to do some more work to figure // out whether it's really a printer DC or a true metafile // DC: if (Globals::GdiIsMetaPrintDCFunction(hdc)) { if (IsPostscriptPrinter(hdc)) { hdcType = PostscriptPrinterHdc; } else { hdcType = PrinterHdc; } } else { hdcType = EmfHdc; } break; case OBJ_METADC: hdcType = WmfHdc; break; default: WARNING(("Unknown HDC type!")); break; } return hdcType; } DWORD GetHdcBitmapBitsPixel( HDC hdc ) { // This function returns the number of bits per pixel for a bitmap DC. // On error, 0 is returned. ASSERT(GetDCType(hdc) == OBJ_MEMDC); HBITMAP hbm = (HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP); if (hbm) { BITMAP bm; if (GetObjectA(hbm, sizeof(bm), &bm) >= sizeof(BITMAP)) { return bm.bmBitsPixel; } } return 0; } MfEnumState::MfEnumState( HDC hdc, BOOL externalEnumeration, InterpolationMode interpolation, GpRecolor * recolor, ColorAdjustType adjustType, const RECT * deviceRect, DpContext * context ) { HdcType = GetHdcType(hdc); Hdc = hdc; HandleTable = NULL; NumObjects = 0; CurrentPalette = (HPALETTE)::GetStockObject(DEFAULT_PALETTE); SizeAllocedRecord = 0; ModifiedRecordSize = 0; SaveDcCount = 0; BytesEnumerated = 0; ExternalEnumeration = externalEnumeration; Recolor = recolor; AdjustType = adjustType; CurrentRecord = NULL; ModifiedRecord = NULL; AllocedRecord = NULL; Interpolation = interpolation; SaveDcVal = SaveDC(hdc); FsmState = MfFsmStart; GdiCentricMode = FALSE; SoftekFilter = FALSE; DefaultFont = NULL; RopUsed = FALSE; Context = context; SrcCopyOnly = FALSE; GpMemset(StockFonts, 0, sizeof(StockFonts[0]) * NUM_STOCK_FONTS); GpMemset(RecoloredStockHandle, 0, sizeof(HGDIOBJ) * NUM_STOCK_RECOLOR_OBJS); // See if we should halftone solid colors if ((IsScreen() && (::GetDeviceCaps(hdc, BITSPIXEL) == 8)) || (IsBitmap() && (GetHdcBitmapBitsPixel(hdc) == 8))) { Is8Bpp = TRUE; EpPaletteMap paletteMap(hdc); IsHalftonePalette = (paletteMap.IsValid() && (!paletteMap.IsVGAOnly())); } else { Is8Bpp = FALSE; IsHalftonePalette = FALSE; } // Since the transform can change as we are playing the metafile // convert the destrect into DeviceUnits. We will make sure to have an // identity matrix when we apply it. DestRectDevice = *deviceRect; } MfEnumState::~MfEnumState() { // Delete all the true type fonts we created (first make sure they // are not selected into the Hdc. ::SelectObject(Hdc, GetStockObject(SYSTEM_FONT)); if (DefaultFont) { DeleteObject(DefaultFont); } for (int i = 0; i < NUM_STOCK_FONTS; i++) { if (StockFonts[i] != NULL) { DeleteObject(StockFonts[i]); } } // Not necessary to delete NULL_BRUSH, NULL_PEN into HDC. The subclasses // restore DC state which should dissolve any selections of these pen/brushes. for (int i = 0; i < NUM_STOCK_RECOLOR_OBJS; i++) { if (RecoloredStockHandle[i] != NULL) { DeleteObject(RecoloredStockHandle[i]); } } } WmfEnumState::WmfEnumState( HDC hdc, HMETAFILE hWmf, BOOL externalEnumeration, InterpolationMode interpolation, const RECT * dstRect, const RECT * deviceRect, DpContext * context, GpRecolor * recolor, ColorAdjustType adjustType ) : MfEnumState(hdc, externalEnumeration, interpolation, recolor, adjustType, deviceRect, context) { if (IsValid()) { IgnorePostscript = FALSE; BytesEnumerated = sizeof(METAHEADER); // in WMF enumeration, we don't get header MetafileSize = GetMetaFileBitsEx(hWmf, 0, NULL); FirstViewportExt = TRUE; FirstViewportOrg = TRUE; IsFirstRecord = TRUE; // The bad thing about this is that if the metafile is being recolored, // the default pen, brush, text color, and background colors have NOT // been recolored. So let's hope they're not really used. HpenSave = (HPEN) ::SelectObject(hdc, GetStockObject(BLACK_PEN)); HbrushSave = (HBRUSH)::SelectObject(hdc, GetStockObject(WHITE_BRUSH)); if (!Globals::IsNt) { HpaletteSave = (HPALETTE)GetCurrentObject(hdc, OBJ_PAL); HfontSave = (HFONT) GetCurrentObject(hdc, OBJ_FONT); HbitmapSave = (HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP); HregionSave = (HRGN) GetCurrentObject(hdc, OBJ_REGION); } else { HpaletteSave = NULL; HfontSave = NULL; HbitmapSave = NULL; HregionSave = NULL; } // Make sure a few default values are set in the hdc ::SetTextAlign(hdc, 0); ::SetTextJustification(hdc, 0, 0); ::SetTextColor(hdc, RGB(0,0,0)); TextColor = RGB(0,0,0); ::SetBkColor(hdc, RGB(255,255,255)); BkColor = RGB(255,255,255); ::SetROP2(hdc, R2_COPYPEN); DstViewportOrg.x = dstRect->left; DstViewportOrg.y = dstRect->top; DstViewportExt.cx = dstRect->right - DstViewportOrg.x; DstViewportExt.cy = dstRect->bottom - DstViewportOrg.y; } } WmfEnumState::~WmfEnumState() { // Turn POSTSCRIPT_IGNORE back off if we need to. if (IsPostscriptPrinter() && IgnorePostscript) { WORD wOn = FALSE; ::Escape(Hdc, POSTSCRIPT_IGNORE, sizeof(wOn), (LPCSTR)&wOn, NULL); } // According to Office GEL, SaveDC/RestoreDC doesn't always restore // the brush and the pen correctly, so we have to do that ourselves. ::SelectObject(Hdc, HbrushSave); ::SelectObject(Hdc, HpenSave); GpFree(AllocedRecord); if (IsMetafile()) { // Account for unbalanced SaveDC/RestoreDC pairs and // restore to the saveDC state ::RestoreDC(Hdc, SaveDcCount - 1); } else { ::RestoreDC(Hdc, SaveDcVal); } } VOID WmfEnumState::EndRecord( ) { // We rely on the number of bytes enumerated to determine if // this is the last record of the WMF. if ((MetafileSize - BytesEnumerated) < SIZEOF_METARECORDHEADER) { if (!Globals::IsNt) { // GDI won't delete objects that are still selected, so // select out all WMF objects before proceeding. ::SelectObject(Hdc, HpenSave); ::SelectObject(Hdc, HbrushSave); ::SelectObject(Hdc, HpaletteSave); ::SelectObject(Hdc, HfontSave); ::SelectObject(Hdc, HbitmapSave); ::SelectObject(Hdc, HregionSave); INT i; HANDLE handle; if (HandleTable != NULL) { for (i = 0; i < NumObjects; i++) { if ((handle = HandleTable->objectHandle[i]) != NULL) { ::DeleteObject(handle); HandleTable->objectHandle[i] = NULL; } } } } } } BOOL WmfEnumState::PlayRecord( ) { const METARECORD * recordToPlay = ModifiedWmfRecord; // See if we've modified the record if (recordToPlay == NULL) { // We haven't. See if we have a valid current record if (CurrentWmfRecord != NULL) { recordToPlay = CurrentWmfRecord; } else { // we don't so we have to create one if (!CreateCopyOfCurrentRecord()) { return FALSE; } recordToPlay = ModifiedWmfRecord; } } return PlayMetaFileRecord(Hdc, HandleTable, (METARECORD *)recordToPlay, NumObjects); } INT MfEnumState::GetModifiedDibSize( BITMAPINFOHEADER UNALIGNED * dibInfo, UINT numPalEntries, UINT dibBitsSize, UINT & usage ) { ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER)); INT byteWidth; INT bitCount = dibInfo->biBitCount; if ((usage == DIB_PAL_COLORS) && ((bitCount > 8) || (dibInfo->biCompression == BI_BITFIELDS))) { usage = DIB_RGB_COLORS; } if ((Recolor != NULL) || (usage == DIB_PAL_COLORS)) { INT biSize = dibInfo->biSize; if (bitCount > 8) { if ((dibInfo->biCompression != BI_RGB) && (dibInfo->biCompression != BI_BITFIELDS)) { return 0; // don't handle compressed images } ASSERT((bitCount == 16) || (bitCount == 24) || (bitCount == 32)); INT posHeight = dibInfo->biHeight; if (posHeight < 0) { posHeight = -posHeight; } // We have to recolor the object, so we will convert it to a // 24 bit image and send it down. // Even if we have less then 256 pixels, it's not worth palettizing // anymore because the palette will be the size of the image anyway // make sure that the bitmap is width is aligned // PERF: We could create a GpBitmap from the bitmap and recolor the // GpBitmap dibBitsSize = posHeight * (((dibInfo->biWidth * 3) + 3) & ~3); numPalEntries = 0; biSize = sizeof(BITMAPINFOHEADER); } else if ((numPalEntries == 0) || (dibInfo->biCompression == BI_CMYK) || (dibInfo->biCompression == BI_CMYKRLE4) || (dibInfo->biCompression == BI_CMYKRLE8)) { return 0; // don't handle CMYK images } usage = DIB_RGB_COLORS; return biSize + (numPalEntries * sizeof(RGBQUAD)) + dibBitsSize; } return 0; // no modifications needed } inline static INT GetMaskShift( INT maskValue ) { ASSERT (maskValue != 0); INT shift = 0; while (((maskValue & 1) == 0) && (shift < 24)) { shift++; maskValue >>= 1; } return shift; } inline static INT GetNumMaskBits( INT maskValue ) { ASSERT ((maskValue & 1) != 0); INT numBits = 0; while ((maskValue & 1) != 0) { numBits++; maskValue >>= 1; } return numBits; } VOID MfEnumState::Modify16BppDib( INT width, INT posHeight, BYTE * srcPixels, DWORD UNALIGNED * bitFields, BYTE * dstPixels, ColorAdjustType adjustType ) { INT rMask = 0x00007C00; // same as GDI default INT gMask = 0x000003E0; INT bMask = 0x0000001F; INT rMaskShift = 10; INT gMaskShift = 5; INT bMaskShift = 0; INT rNumBits = 5; INT gNumBits = 5; INT bNumBits = 5; INT rRightShift = 2; INT gRightShift = 2; INT bRightShift = 2; if (bitFields != NULL) { rMask = (INT)((WORD)(*bitFields++)); gMask = (INT)((WORD)(*bitFields++)); bMask = (INT)((WORD)(*bitFields)); rMaskShift = GetMaskShift(rMask); gMaskShift = GetMaskShift(gMask); bMaskShift = GetMaskShift(bMask); rNumBits = GetNumMaskBits(rMask >> rMaskShift); gNumBits = GetNumMaskBits(gMask >> gMaskShift); bNumBits = GetNumMaskBits(bMask >> bMaskShift); rRightShift = (rNumBits << 1) - 8; gRightShift = (gNumBits << 1) - 8; bRightShift = (bNumBits << 1) - 8; } INT palIndex = 0; INT pixel; INT r, g, b; COLORREF color; INT w, h; INT srcByteWidth = ((width * 2) + 3) & (~3); INT dstByteWidth = ((width * 3) + 3) & (~3); for (h = 0; h < posHeight; h++) { for (w = 0; w < width; w++) { pixel = (INT)(((INT16 *)srcPixels)[w]); r = (pixel & rMask) >> rMaskShift; r = (r | (r << rNumBits)) >> rRightShift; g = (pixel & gMask) >> gMaskShift; g = (g | (g << gNumBits)) >> gRightShift; b = (pixel & bMask) >> bMaskShift; b = (b | (b << bNumBits)) >> bRightShift; color = ModifyColor(RGB(r, g, b), adjustType); dstPixels[3*w + 2] = GetRValue(color); dstPixels[3*w + 1] = GetGValue(color); dstPixels[3*w] = GetBValue(color); } srcPixels += srcByteWidth; dstPixels += dstByteWidth; } } inline static INT Get24BppColorIndex( INT maskValue ) { switch(GetMaskShift(maskValue)) { default: WARNING(("Invalid BitFields Mask")); // FALLTHRU case 0: return 0; case 8: return 1; case 16: return 2; } } VOID MfEnumState::Modify24BppDib( INT width, INT posHeight, BYTE * srcPixels, DWORD UNALIGNED * bitFields, BYTE * dstPixels, ColorAdjustType adjustType ) { INT rIndex = 2; INT gIndex = 1; INT bIndex = 0; if (bitFields != NULL) { INT rMask = (INT)((*bitFields++)); INT gMask = (INT)((*bitFields++)); INT bMask = (INT)((*bitFields)); rIndex = Get24BppColorIndex(rMask); gIndex = Get24BppColorIndex(gMask); bIndex = Get24BppColorIndex(bMask); } INT palIndex = 0; INT r, g, b; COLORREF color; INT w, h; INT srcByteWidth = ((width * 3) + 3) & (~3); INT dstByteWidth = ((width * 3) + 3) & (~3); BYTE * srcRaster = srcPixels; for (h = 0; h < posHeight; h++) { srcPixels = srcRaster; for (w = 0; w < width; w++) { r = srcPixels[rIndex]; g = srcPixels[gIndex]; b = srcPixels[bIndex]; srcPixels += 3; color = ModifyColor(RGB(r, g, b), adjustType); dstPixels[3*w + 2] = GetRValue(color); dstPixels[3*w + 1] = GetGValue(color); dstPixels[3*w] = GetBValue(color); } srcRaster += srcByteWidth; dstPixels += dstByteWidth; } } inline static INT Get32BppColorIndex( INT maskValue ) { switch(GetMaskShift(maskValue)) { default: WARNING(("Invalid BitFields Mask")); // FALLTHRU case 0: return 0; case 8: return 1; case 16: return 2; case 24: return 3; } } VOID MfEnumState::Modify32BppDib( INT width, INT posHeight, BYTE * srcPixels, DWORD UNALIGNED * bitFields, BYTE * dstPixels, ColorAdjustType adjustType ) { INT rIndex = 2; INT gIndex = 1; INT bIndex = 0; if (bitFields != NULL) { INT rMask = (INT)((*bitFields++)); INT gMask = (INT)((*bitFields++)); INT bMask = (INT)((*bitFields)); rIndex = Get32BppColorIndex(rMask); gIndex = Get32BppColorIndex(gMask); bIndex = Get32BppColorIndex(bMask); } INT palIndex = 0; INT r, g, b; COLORREF color; INT w, h; INT dstByteWidth = ((width * 3) + 3) & (~3); for (h = 0; h < posHeight; h++) { for (w = 0; w < width; w++) { r = srcPixels[rIndex]; g = srcPixels[gIndex]; b = srcPixels[bIndex]; srcPixels += 4; color = ModifyColor(RGB(r, g, b), adjustType); dstPixels[3*w + 2] = GetRValue(color); dstPixels[3*w + 1] = GetGValue(color); dstPixels[3*w] = GetBValue(color); } dstPixels += dstByteWidth; } } VOID MfEnumState::ModifyDib( UINT usage, BITMAPINFOHEADER UNALIGNED * srcDibInfo, BYTE * srcBits, // if NULL, it's a packed DIB BITMAPINFOHEADER UNALIGNED * dstDibInfo, UINT numPalEntries, UINT srcDibBitsSize, ColorAdjustType adjustType ) { INT srcBitCount = srcDibInfo->biBitCount; BYTE * srcPixels = srcBits; COLORREF color; if (srcBitCount <= 8) { GpMemcpy(dstDibInfo, srcDibInfo, srcDibInfo->biSize); RGBQUAD UNALIGNED * srcRgb = GetDibColorTable(srcDibInfo); RGBQUAD UNALIGNED * dstRgb = GetDibColorTable(dstDibInfo); dstDibInfo->biClrUsed = numPalEntries; if ((usage == DIB_PAL_COLORS) && (dstDibInfo->biCompression != BI_BITFIELDS)) { WORD * srcPal = (WORD *)srcRgb; if (srcPixels == NULL) { srcPixels = (BYTE *)(srcPal + ((numPalEntries + 1) & ~1)); // align } // Copy the Dib pixel data GpMemcpy(dstRgb + numPalEntries, srcPixels, srcDibBitsSize); // Modify the palette colors while (numPalEntries--) { color = ModifyColor(*srcPal++ | 0x01000000, adjustType); dstRgb->rgbRed = GetRValue(color); dstRgb->rgbGreen = GetGValue(color); dstRgb->rgbBlue = GetBValue(color); dstRgb->rgbReserved = 0; dstRgb++; } } else { if (srcPixels == NULL) { srcPixels = (BYTE *)(srcRgb + numPalEntries); } // Copy the Dib pixel data GpMemcpy(dstRgb + numPalEntries, srcPixels, srcDibBitsSize); // Modify the palette colors while (numPalEntries--) { color = ModifyColor(RGB(srcRgb->rgbRed, srcRgb->rgbGreen, srcRgb->rgbBlue), adjustType); dstRgb->rgbRed = GetRValue(color); dstRgb->rgbGreen = GetGValue(color); dstRgb->rgbBlue = GetBValue(color); dstRgb->rgbReserved = 0; dstRgb++; srcRgb++; } } } else // Recolor the bitmap. There is no need to palettize the image since // the palette will be as big as the image { INT posHeight = srcDibInfo->biHeight; if (posHeight < 0) { posHeight = -posHeight; } ASSERT((srcDibInfo->biCompression == BI_RGB) || (srcDibInfo->biCompression == BI_BITFIELDS)); GpMemset(dstDibInfo, 0, sizeof(BITMAPINFOHEADER)); dstDibInfo->biSize = sizeof(BITMAPINFOHEADER); dstDibInfo->biWidth = srcDibInfo->biWidth; dstDibInfo->biHeight = srcDibInfo->biHeight; dstDibInfo->biPlanes = 1; dstDibInfo->biBitCount = 24; BYTE * dstPixels = GetDibBits(dstDibInfo,0,0); DWORD UNALIGNED * bitFields = NULL; if (srcPixels == NULL) { srcPixels = (BYTE *)GetDibBits(srcDibInfo, numPalEntries, usage); } dstDibInfo->biClrUsed = 0; dstDibInfo->biClrImportant = 0; if (numPalEntries == 3) { ASSERT((srcBitCount == 16) || (srcBitCount == 32)); bitFields = (DWORD*) GetDibColorTable(srcDibInfo); if ((bitFields[0] == 0) || (bitFields[1] == 0) || (bitFields[2] == 0)) { bitFields = NULL; } } else if (srcDibInfo->biSize >= sizeof(BITMAPV4HEADER)) { BITMAPV4HEADER * srcHeaderV4 = (BITMAPV4HEADER *)srcDibInfo; if ((srcHeaderV4->bV4RedMask != 0) && (srcHeaderV4->bV4GreenMask != 0) && (srcHeaderV4->bV4BlueMask != 0)) { bitFields = &(srcHeaderV4->bV4RedMask); } } switch (srcBitCount) { case 16: Modify16BppDib(srcDibInfo->biWidth, posHeight, srcPixels, bitFields, dstPixels, adjustType); break; case 24: Modify24BppDib(srcDibInfo->biWidth, posHeight, srcPixels, bitFields, dstPixels, adjustType); break; case 32: Modify32BppDib(srcDibInfo->biWidth, posHeight, srcPixels, bitFields, dstPixels, adjustType); break; } } } VOID WmfEnumState::DibCreatePatternBrush( ) { INT style = (INT)((INT16)(((WORD *)RecordData)[0])); UINT usage = (UINT)((UINT16)(((WORD *)RecordData)[1])); BITMAPINFOHEADER UNALIGNED * srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(&(((WORD *)RecordData)[2])); UINT numPalEntries; // Pattern brush should mean that it is a monochrome DIB if (style == BS_PATTERN) { if (Recolor != NULL) { DWORD UNALIGNED * rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo); // See if it is a monochrome pattern brush. If it is, then // the text color will be used for 0 bits and the background // color will be used for 1 bits. These colors are already // modified by their respective records, so there is no need // to do anything here. // If it is not a monochrome pattern brush, just create a // solid black brush. if ((usage != DIB_RGB_COLORS) || (srcDibInfo->biSize < sizeof(BITMAPINFOHEADER)) || !GetDibNumPalEntries(TRUE, srcDibInfo->biSize, srcDibInfo->biBitCount, srcDibInfo->biCompression, srcDibInfo->biClrUsed, &numPalEntries) || (numPalEntries != 2) || (srcDibInfo->biBitCount != 1) || (srcDibInfo->biPlanes != 1) || (rgb[0] != 0x00000000) || (rgb[1] != 0x00FFFFFF)) { // This shouldn't happen, at least not if recorded on NT WARNING(("Non-monochrome pattern brush")); MakeSolidBlackBrush(); } } } else { UINT dibBitsSize; if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) && GetDibNumPalEntries(TRUE, srcDibInfo->biSize, srcDibInfo->biBitCount, srcDibInfo->biCompression, srcDibInfo->biClrUsed, &numPalEntries) && ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0)) { UINT oldUsage = usage; INT dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage); if (dstDibSize > 0) { INT size = SIZEOF_METARECORDHEADER + (2 * sizeof(WORD)) + dstDibSize; CreateRecordToModify(size); ModifiedWmfRecord->rdSize = size / 2; ModifiedWmfRecord->rdFunction = META_DIBCREATEPATTERNBRUSH; ModifiedWmfRecord->rdParm[0] = BS_DIBPATTERN; ModifiedWmfRecord->rdParm[1] = DIB_RGB_COLORS; ModifyDib(oldUsage, srcDibInfo, NULL, (BITMAPINFOHEADER UNALIGNED *)(&(ModifiedWmfRecord->rdParm[2])), numPalEntries, dibBitsSize, ColorAdjustTypeBrush); } } } this->PlayRecord(); } // This record is obsolete, because it uses a compatible bitmap // instead of a DIB. It has a BITMAP16 structure that is // used to call CreateBitmapIndirect. That HBITMAP is, in turn, // used to call CreatePatternBrush. If this record is present, // it is likely that the bitmap is monochrome, in which case // the TextColor and the BkColor will be used, and these colors // already get modified by their respective records. VOID WmfEnumState::CreatePatternBrush( ) { WARNING(("Obsolete META_CREATEPATTERNBRUSH record")); BITMAP16 UNALIGNED * bitmap = (BITMAP16 UNALIGNED *)RecordData; if (bitmap->bmBitsPixel != 1) { WARNING(("Non-monochrome pattern brush")); MakeSolidBlackBrush(); } this->PlayRecord(); } VOID WmfEnumState::CreatePenIndirect( ) { LOGPEN16 UNALIGNED * logPen = (LOGPEN16 UNALIGNED *)RecordData; switch (logPen->lopnStyle) { default: WARNING(("Unrecognized Pen Style")); case PS_NULL: break; // leave the pen alone case PS_SOLID: case PS_INSIDEFRAME: case PS_DASH: case PS_DOT: case PS_DASHDOT: case PS_DASHDOTDOT: ModifyRecordColor(3, ColorAdjustTypePen); break; } this->PlayRecord(); } VOID WmfEnumState::CreateBrushIndirect( ) { LOGBRUSH16 UNALIGNED * logBrush = (LOGBRUSH16 UNALIGNED *)RecordData; switch (logBrush->lbStyle) { case BS_SOLID: case BS_HATCHED: { ModifyRecordColor(1, ColorAdjustTypeBrush); if (ModifiedWmfRecord != NULL) { logBrush = (LOGBRUSH16 UNALIGNED *)(ModifiedWmfRecord->rdParm); } // See if we need to halftone the color. We do if it is a solid // color, and we have a halftone palette, and the color is not // an exact match in the palette. COLORREF color; if (IsHalftonePalette && (logBrush->lbStyle == BS_SOLID) && (((color = logBrush->lbColor) & 0x02000000) == 0)) { // create a halftone brush, instead of a solid brush INT size = SIZEOF_METARECORDHEADER + (2 * sizeof(WORD)) + sizeof(BITMAPINFOHEADER) + // DIB 8 bpp header (8 * sizeof(RGBQUAD)) + // DIB 8 colors (8 * 8); // DIB 8x8 pixels ModifiedRecordSize = 0; // in case we already modified the record CreateRecordToModify(size); ModifiedWmfRecord->rdSize = size / 2; ModifiedWmfRecord->rdFunction = META_DIBCREATEPATTERNBRUSH; ModifiedWmfRecord->rdParm[0] = BS_DIBPATTERN; ModifiedWmfRecord->rdParm[1] = DIB_RGB_COLORS; HalftoneColorRef_216(color, &(ModifiedWmfRecord->rdParm[2])); } } break; case BS_HOLLOW: break; // leave the record alone default: // Looking at the NT source code, there shouldn't be any // other brush styles for an indirect brush. WARNING(("Brush Style Not Valid")); MakeSolidBlackBrush(); break; } this->PlayRecord(); } // Also handles StretchBlt. // These records are obsolete (when there is a source bitmap) because they // have a compatible bitmap instead of a DIB. For that reason, we don't // recolor them. VOID WmfEnumState::BitBlt( ) { DWORD rop = *((UNALIGNED DWORD *)RecordData); // If No-Op ROP, do nothing; just return if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000)) { return; } if (!IsMetafile()) { if (IsSourceInRop3(rop)) { WARNING(("Obsolete META_BITBLT/META_STRETCHBLT record")); if ((rop != SRCCOPY) && SrcCopyOnly && CreateCopyOfCurrentRecord()) { *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY; } } else { if ((rop != PATCOPY) && SrcCopyOnly && CreateCopyOfCurrentRecord()) { *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = PATCOPY; } } } this->PlayRecord(); } VOID WmfEnumState::Escape( ) { INT escapeCode = (INT)((INT16)(((WORD *)RecordData)[0])); if (!IsPostscript()) { if (SkipEscape(escapeCode)) { return; } // Skip Office Art data when playing into another metafile if (IsMetafile() && IsOfficeArtData(GetCurrentRecordSize(), (WORD *)RecordData)) { return; } } else // it is postscript { if (escapeCode == MFCOMMENT) { if (GetWmfComment((WORD *)RecordData, msosignature, msocommentBeginSrcCopy)) { SrcCopyOnly = TRUE; return; } if (GetWmfComment((WORD *)RecordData, msosignature, msocommentEndSrcCopy)) { SrcCopyOnly = FALSE; return; } } if (escapeCode == POSTSCRIPT_DATA) { // Bug #98743 (Windows Bugs) Gdiplus must overcome GDI limitation // with POSTSCRIPT_INJECTION. Comments from Rammanohar Arumugam: // // Being in xx-centric mode means POSTSCRIPT_DATA won't work. I // take that to mean that PlayMetaFileRecord only works in // compatibility mode. // // GdiPlus will check for the printer mode. In GDI-centric and // Postscript-centric mode, it will not do PlayMetaFileRecord for // any record that has POSTSCRIPT_DATA. Instead, it will output // the postscript data through a PASSTHRU (for GDI-centric mode) // or a POSTSCRIPT_PASSTHRU (for Postscript-Centric mode). // // You can find out the mode by querying the escape support. // 1. Query for POSTSCRIPT_INJECTION support. If not supported, // it's compat mode. If supported, find out the mode by doing step 2/3 // 2. Query for PASSTHROUGH support. If supported, it's GDI-centric. // 3. Query for POSTSCRIPT_PASSTHROUGH support. If supported, it's // PS-centric. if (Globals::IsNt) { if (!SoftekFilter) { // Determine presence of Softek Filter EPS, if so, then // we apply workaround patches. WORD size = *((WORD*)RecordData); LPSTR escape = (LPSTR)(&RecordData[6]); const LPSTR softekString = "%MSEPS Preamble [Softek"; INT softekLen = strlen(softekString); if (size >= softekLen) { SoftekFilter = !GpMemcmp(softekString, escape, softekLen); } } DWORD EscapeValue = POSTSCRIPT_IDENTIFY; if (::ExtEscape(Hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue, 0, NULL) <= 0) { // POSTSCRIPT_IDENTITY is not supported if the mode has // been set because it can only be set once. EscapeValue = POSTSCRIPT_PASSTHROUGH; if (::ExtEscape(Hdc, QUERYESCSUPPORT, sizeof(DWORD), (LPSTR)&EscapeValue, 0, NULL) <= 0) { // GDI-centric mode if (CreateCopyOfCurrentRecord()) { *((WORD *)ModifiedWmfRecord->rdParm) = PASSTHROUGH; } GdiCentricMode = TRUE; } else { // PS-centric mode if (CreateCopyOfCurrentRecord()) { *((WORD *)ModifiedWmfRecord->rdParm) = POSTSCRIPT_PASSTHROUGH; } } this->PlayRecord(); return; } else { // compatibility mode, uses POSTSCRIPT_DATA } } else { // Win98 doesn't distinguish between GDI & compatibility mode if (CreateCopyOfCurrentRecord()) { *((WORD *)ModifiedWmfRecord->rdParm) = PASSTHROUGH; } } } } // Keep track of the POSTSCRIPT_IGNORE state. If it is still on at // the of the metafile, then turn it OFF if (escapeCode == POSTSCRIPT_IGNORE && IsPostscript()) { IgnorePostscript = ((WORD *)RecordData)[2] ? TRUE : FALSE; } this->PlayRecord(); } VOID WmfEnumState::Rectangle( ) { if (FsmState == MfFsmSetROP) { // There is a bug using PlayMetaFileRecord on Win2K for this // type of escape, we must explicitly call ExtEscape. See bug // #98743. WORD* rdParm = (WORD*)&(RecordData[0]); CHAR postscriptEscape[512]; RECT rect; rect.left = (SHORT)rdParm[3]; rect.top = (SHORT)rdParm[2]; rect.right = (SHORT)rdParm[1]; rect.bottom = (SHORT)rdParm[0]; if (LPtoDP(Hdc, (POINT*)&rect, 2)) { // Some injected postscript, strangely enough contains the equivalent // of a stroke which is erroroneously executed on the current path. In // one case, bug #281856 it results in a border about the object. To // get around this, we output a 'N' which is newpath operator if it's // defined (which should be always.) This, incidently, is done when // calling GDI Rectangle() succeeds, it outputs "N x y w h B", so we // are doing the equivalent here. GpRect clipRect; Context->VisibleClip.GetBounds(&clipRect); wsprintfA(&postscriptEscape[2], "\r\n%d %d %d %d CB\r\n" "%s" "%d %d %d %d B\r\n", clipRect.Width, clipRect.Height, clipRect.X, clipRect.Y, Globals::IsNt ? "newpath\r\n" : "", rect.right - rect.left, rect.bottom - rect.top, rect.left, rect.top); ASSERT(strlen(&postscriptEscape[2]) < 512); *(WORD*)(&postscriptEscape[0]) = (WORD)(strlen(&postscriptEscape[2])); ::ExtEscape(Hdc, PASSTHROUGH, *(WORD*)(&postscriptEscape[0]) + sizeof(WORD) + 1, (CHAR*)&postscriptEscape[0], 0, NULL); return; } } this->PlayRecord(); } VOID WmfEnumState::RestoreHdc( ) { INT relativeCount = (INT)((INT16)(((WORD *)RecordData)[0])); if (SaveDcCount < 0) { if (relativeCount >= SaveDcCount) { if (relativeCount >= 0) { // Modify the record CreateCopyOfCurrentRecord(); // guaranteed to succeed relativeCount = -1; ModifiedWmfRecord->rdParm[0] = (INT16)(-1); } } else { // Modify the record CreateCopyOfCurrentRecord(); // guaranteed to succeed relativeCount = SaveDcCount; ModifiedWmfRecord->rdParm[0] = (INT16)(relativeCount); } SaveDcCount -= relativeCount; this->PlayRecord(); } else { WARNING(("RestoreDC not matched to a SaveDC")); } } // The rop for this command is always SRCCOPY VOID WmfEnumState::SetDIBitsToDevice( ) { // !!! // Office doesn't do anything with this record. For now, I don't think // I will either. It's a tough one to deal with for a couple reasons: // 1st - The xDest and yDest values are in world units, but the // width and height values are in device units // (unlike StretchDIBits). // 2nd - The amount of bits data present may be different than // what is in the DIB header (based on the cScanLines param). // This makes it harder to deal with as a packed DIB. this->PlayRecord(); } VOID MfEnumState::SelectPalette( INT objectIndex ) { // For EMF the check really should be > 0 if ((objectIndex >= 0) && (objectIndex < NumObjects) && (HandleTable != NULL)) { HGDIOBJ hPal = HandleTable->objectHandle[objectIndex]; if ((hPal != NULL) && (GetObjectTypeInternal(hPal) == OBJ_PAL)) { CurrentPalette = (HPALETTE)hPal; return; } } WARNING(("SelectPalette Failure")); } inline static VOID Point32FromPoint16( POINTL * dstPoints, POINTS UNALIGNED * srcPoints, UINT numPoints ) { for (UINT i = 0; i < numPoints; i++, dstPoints++, srcPoints++) { dstPoints->x = (INT)((INT16)(srcPoints->x)); dstPoints->y = (INT)((INT16)(srcPoints->y)); } } // Apparently there is a bug on Win9x with PolyPolygons, so we // parse the record ourselves. VOID WmfEnumState::PolyPolygon( ) { UINT numPolygons = ((WORD *)RecordData)[0]; UINT numPoints = 0; UINT i; for (i = 0; i < numPolygons; i++) { numPoints += ((LPWORD)&((WORD *)RecordData)[1])[i]; } INT * polyCounts; POINTL * points; INT size = (numPolygons * sizeof(INT)) + (numPoints * sizeof(POINTL)); if (CreateRecordToModify(size)) { polyCounts = (INT *)ModifiedRecord; points = (POINTL *)(polyCounts + numPolygons); for (i = 0; i < numPolygons; i++) { polyCounts[i] = (INT)(UINT)((LPWORD)&((WORD *)RecordData)[1])[i]; } Point32FromPoint16(points, (POINTS UNALIGNED *)(((WORD *)RecordData) + numPolygons + 1), numPoints); ::PolyPolygon(Hdc, (POINT *)points, polyCounts, numPolygons); return; } this->PlayRecord(); } #ifdef NEED_TO_KNOW_IF_BITMAP static INT GetBppFromMemDC( HDC hMemDC ) { HBITMAP hBitmap = (HBITMAP)::GetCurrentObject(hMemDC, OBJ_BITMAP); BITMAP bitmap; if ((hBitmap == NULL) || (::GetObjectA(hBitmap, sizeof(bitmap), &bitmap) == 0)) { WARNING(("Couldn't get Bitmap object")); return 0; // error } if (bitmap.bmPlanes <= 0) { WARNING(("Bitmap with no planes")); bitmap.bmPlanes = 1; } INT bpp = bitmap.bmPlanes * bitmap.bmBitsPixel; if (bpp > 32) { WARNING(("Bitmap with too many bits")); bpp = 32; } return bpp; } #endif #define GDI_INTERPOLATION_MAX (1 << 23) VOID MfEnumState::OutputDIB( HDC hdc, const RECTL * bounds, INT dstX, INT dstY, INT dstWidth, INT dstHeight, INT srcX, INT srcY, INT srcWidth, INT srcHeight, BITMAPINFOHEADER UNALIGNED * dibInfo, BYTE * bits, // if NULL, this is a packed DIB UINT usage, DWORD rop, BOOL isWmfDib ) { BITMAPINFO dibHeaderBuffer[1]; // To be sure it's aligned for 64Bits BOOL restoreColors = FALSE; COLORREF oldBkColor; COLORREF oldTextColor; ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER)); if (bits == NULL) { UINT numPalEntries; if (GetDibNumPalEntries(isWmfDib, dibInfo->biSize, dibInfo->biBitCount, dibInfo->biCompression, dibInfo->biClrUsed, &numPalEntries)) { bits = GetDibBits(dibInfo, numPalEntries, usage); } else { WARNING(("GetDibNumPalEntries failure")); return; } } INT posDstWidth = dstWidth; if (posDstWidth < 0) { posDstWidth = -posDstWidth; } INT posDstHeight = dstHeight; if (posDstHeight < 0) { posDstHeight = -posDstHeight; } INT stretchBltMode = HALFTONE; GpBitmap * destBitmap = NULL; POINT destPoints[3]; BitmapData bmpData; BitmapData * bmpDataPtr = NULL; HBITMAP hBitmap = NULL; BYTE * bmpBits = NULL; BITMAPINFO * dibBmpInfo = NULL; BOOL deleteDIBSection = FALSE; // Don't use GDI+ stretching for a mask // Make this the first thing so that we are sure that they are set if ((dibInfo->biBitCount == 1) && (rop != SRCCOPY)) { oldBkColor = ::SetBkColor(hdc, BkColor); oldTextColor = ::SetTextColor(hdc, TextColor); restoreColors = TRUE; goto DoGdiStretch; } // On Win9x we need to create an play a comment record so that the transform // gets invalidated and recalculated if (!Globals::IsNt && !IsMetafile()) { CreateAndPlayCommentRecord(); } destPoints[0].x = dstX; destPoints[0].y = dstY; destPoints[1].x = dstX + posDstWidth; destPoints[1].y = dstY; destPoints[2].x = dstX; destPoints[2].y = dstY + posDstHeight; if (!::LPtoDP(hdc, destPoints, 3)) { goto DoGdiStretch; } posDstWidth = ::GetIntDistance(destPoints[0], destPoints[1]); posDstHeight = ::GetIntDistance(destPoints[0], destPoints[2]); if ((posDstWidth == 0) || (posDstHeight == 0)) { return; } INT posSrcWidth; INT srcWidthSign; posSrcWidth = srcWidth; srcWidthSign = 1; if (posSrcWidth < 0) { posSrcWidth = -posSrcWidth; srcWidthSign = -1; } INT posSrcHeight; INT srcHeightSign; posSrcHeight = srcHeight; srcHeightSign = 1; if (posSrcHeight < 0) { posSrcHeight = -posSrcHeight; srcHeightSign = -1; } INT posSrcDibWidth; posSrcDibWidth = dibInfo->biWidth; if (posSrcDibWidth <= 0) { WARNING(("Bad biWidth value")); return; // negative source dib width not allowed } INT posSrcDibHeight; posSrcDibHeight = dibInfo->biHeight; if (posSrcDibHeight < 0) { posSrcDibHeight = -posSrcDibHeight; } // We can have a negative source Width or height // we need to verify that the two corners of the srcRect lie in the // bitmap bounds if (srcX < 0) { srcX = 0; WARNING(("srcX < 0")); } if (srcX > posSrcDibWidth) { WARNING(("Bad srcWidth or srcX value")); srcX = posSrcDibWidth; } if (srcY < 0) { srcY = 0; WARNING(("srcY < 0")); } if (srcY > posSrcDibHeight) { WARNING(("Bad srcWidth or srcX value")); srcY = posSrcDibHeight; } INT srcRight; srcRight = srcX + srcWidth; if (srcRight < 0) { WARNING(("Bad srcWidth or srcX value")); srcWidth = -srcX; } if(srcRight > posSrcDibWidth) { WARNING(("Bad srcWidth or srcX value")); srcWidth = posSrcDibWidth - srcX; } INT srcBottom; srcBottom = srcY + srcHeight; if (srcBottom < 0) { WARNING(("Bad srcWidth or srcX value")); srcHeight = -srcY; } if (srcBottom > posSrcDibHeight) { WARNING(("Bad srcWidth or srcX value")); srcHeight = posSrcDibHeight - srcY; } // This also catches the case where // (posSrcDibWidth == 0) || (posSrcDibHeight == 0) if ((posSrcWidth <= 0) || (posSrcHeight <= 0)) { return; } // If we are drawing into an 8Bpp surface and we have a different ROP then // srcCopy. if (Is8Bpp && rop != SRCCOPY) { BOOL freeDibInfo = FALSE; UINT size; BITMAPINFO * alignedDibInfo = NULL; if (GetDibNumPalEntries(TRUE, dibInfo->biSize, dibInfo->biBitCount, dibInfo->biCompression, dibInfo->biClrUsed, &size)) { if (IsDwordAligned(dibInfo)) { alignedDibInfo = (BITMAPINFO*) dibInfo; } else { // Mutliply the number of entries by the size of each entry size *= ((usage==DIB_RGB_COLORS)?sizeof(RGBQUAD):sizeof(WORD)); // WMF's can't use System Palette alignedDibInfo = (BITMAPINFO*) GpMalloc(dibInfo->biSize + size); if (alignedDibInfo != NULL) { memcpy((void*)&alignedDibInfo, dibInfo, dibInfo->biSize + size); freeDibInfo = TRUE; } } if (alignedDibInfo != NULL) { if (GpBitmap::DrawAndHalftoneForStretchBlt(hdc, alignedDibInfo, bits, srcX, srcY, posSrcWidth, posSrcHeight, posDstWidth, posDstHeight, &dibBmpInfo, &bmpBits, &hBitmap, Interpolation) == Ok) { deleteDIBSection = TRUE; srcX = 0; srcY = 0; srcWidth = posDstWidth; srcHeight = posDstHeight; dibInfo = (BITMAPINFOHEADER*) dibBmpInfo; bits = bmpBits; if (freeDibInfo) { GpFree(alignedDibInfo); } goto DoGdiStretch; } if (freeDibInfo) { GpFree(alignedDibInfo); } } } } // if not stretching, let GDI do the blt if ((posSrcWidth == posDstWidth) && (posSrcHeight == posDstHeight)) { goto DoGdiStretch; } InterpolationMode interpolationMode; interpolationMode = Interpolation; // if not going to the screen or to a bitmap, use GDI to do the stretch // otherwise, use GDI+ to do the stretch (but let GDI do the blt) // if going to printer on Win98 and it is RLE8 compressed bitmap, then // always decode and blit from GDI+. The reason is that print drivers // commonly don't support RLEx encoding and punt to GDI. In this // context it creates a compatible printer dc and bitmap and then does a // StretchBlt, but it only does this at 1bpp, the result is black and white. if ((IsPrinter() && !Globals::IsNt && dibInfo->biCompression == BI_RLE8) || ((IsScreen() || IsBitmap()) && (interpolationMode != InterpolationModeNearestNeighbor) && (posSrcWidth > 1) && (posSrcHeight > 1) && ((posDstWidth * posDstHeight) < GDI_INTERPOLATION_MAX))) { GpStatus status = GenericError; destBitmap = new GpBitmap(posDstWidth, posDstHeight, PIXFMT_24BPP_RGB); if (destBitmap != NULL) { if (destBitmap->IsValid()) { BITMAPINFO * alignedDibInfo = NULL; UINT size; BOOL freeDibInfo = FALSE; if (GetDibNumPalEntries(TRUE, dibInfo->biSize, dibInfo->biBitCount, dibInfo->biCompression, dibInfo->biClrUsed, &size)) { if (IsDwordAligned(dibInfo)) { alignedDibInfo = (BITMAPINFO*) dibInfo; } else { // Mutliply the number of entries by the size of each entry size *= ((usage==DIB_RGB_COLORS)?sizeof(RGBQUAD):sizeof(WORD)); // WMF's can't use System Palette alignedDibInfo = (BITMAPINFO*) GpMalloc(dibInfo->biSize + size); if (alignedDibInfo != NULL) { memcpy((void*)&alignedDibInfo, dibInfo, dibInfo->biSize + size); freeDibInfo = TRUE; } } if (alignedDibInfo != NULL) { GpBitmap * srcBitmap = new GpBitmap(alignedDibInfo, bits, FALSE); if (srcBitmap != NULL) { if (srcBitmap->IsValid()) { GpGraphics * destGraphics = destBitmap->GetGraphicsContext(); if (destGraphics != NULL) { if (destGraphics->IsValid()) { // we have to lock the graphics so the driver doesn't assert GpLock lockGraphics(destGraphics->GetObjectLock()); ASSERT(lockGraphics.IsValid()); GpRectF dstRect(0.0f, 0.0f, (REAL)posDstWidth, (REAL)posDstHeight); GpRectF srcRect; // StretchDIBits takes a srcY parameter relative to the lower-left // (bottom) corner of the bitmap, not the top-left corner, // like DrawImage does srcRect.Y = (REAL)(posSrcDibHeight - srcY - srcHeight); srcRect.X = (REAL)srcX; // !!! We have to subtract one to keep the // filter from blending black into the image // on the right and bottom. srcRect.Width = (REAL)(srcWidth - (srcWidthSign)); srcRect.Height = (REAL)(srcHeight - (srcHeightSign)); // don't do any blending as part of the stretch destGraphics->SetCompositingMode(CompositingModeSourceCopy); destGraphics->SetInterpolationMode(interpolationMode); // Set the image attributes to Wrap since we don't want to // use black pixels for the edges GpImageAttributes imgAttr; imgAttr.SetWrapMode(WrapModeTileFlipXY); // now draw the source into the dest bitmap status = destGraphics->DrawImage(srcBitmap, dstRect, srcRect, UnitPixel, &imgAttr); } else { WARNING(("destGraphics not valid")); } delete destGraphics; } else { WARNING(("Could not construct destGraphics")); } } else { WARNING(("srcGraphics not valid")); } srcBitmap->Dispose(); // doesn't delete the source data } else { WARNING(("Could not allocate a new BitmapInfoHeader")); } if (freeDibInfo) { GpFree(alignedDibInfo); } } else { WARNING(("Could not construct srcGraphics")); } } else { WARNING(("Could not clone the bitmap header")); } if ((status == Ok) && (destBitmap->LockBits(NULL, IMGLOCK_READ, PIXFMT_24BPP_RGB, &bmpData) == Ok)) { ASSERT((bmpData.Stride & 3) == 0); GpMemset(dibHeaderBuffer, 0, sizeof(BITMAPINFO)); bmpDataPtr = &bmpData; bits = (BYTE *)bmpData.Scan0; srcX = 0; srcY = 0; srcWidth = posDstWidth; srcHeight = posDstHeight; usage = DIB_RGB_COLORS; dibInfo = (BITMAPINFOHEADER *)dibHeaderBuffer; dibInfo->biSize = sizeof(BITMAPINFOHEADER); dibInfo->biWidth = posDstWidth; dibInfo->biHeight = -posDstHeight; dibInfo->biPlanes = 1; dibInfo->biBitCount = 24; // We don't want to set the StretchBltMode to COLORONCOLOR here // because we might draw this into non 8Bpp surface and we still // have to halftone in this case } } else { WARNING(("destBitmap not valid")); } } else { WARNING(("Could not construct destBitmap")); } } DoGdiStretch: // Halftoning on NT4 with 8bpp does not work very well -- it gets // the wrong color hue. // We cannot halftone to metafile for that same reason if ((rop != SRCCOPY) || (Is8Bpp && Globals::IsNt && (Globals::OsVer.dwMajorVersion <= 4)) || IsMetafile()) { // don't let halftoning mess up some kind of masking or other effect stretchBltMode = COLORONCOLOR; } ::SetStretchBltMode(hdc, stretchBltMode); // There is a bug in Win9x that some StretchDIBits calls don't work // so we need to create an actual StretchDIBits record to play. // Also, NT4 postscript printing can't handle anything but SRCCOPY, // so change any ROPs that are not source copy. BOOL processed = FALSE; if (!Globals::IsNt) { processed = CreateAndPlayOutputDIBRecord(hdc, bounds, dstX, dstY, dstWidth, dstHeight, srcX, srcY, srcWidth, srcHeight, dibInfo, bits, usage, rop); } else if (rop != SRCCOPY && Globals::OsVer.dwMajorVersion <= 4 && IsPostscript()) { rop = SRCCOPY; } // In MSO, at this point they would check if this is NT running // on a non-true-color surface. If so, they would set the // color adjustment gamma to be 20000. The comment said that // this was needed for NT 3.5. I'm assuming we don't need to // worry about that anymore. if (!processed) { ::StretchDIBits(hdc, dstX, dstY, dstWidth, dstHeight, srcX, srcY, srcWidth, srcHeight, bits, (BITMAPINFO *)dibInfo, usage, rop); } if (destBitmap) { if (bmpDataPtr != NULL) { destBitmap->UnlockBits(bmpDataPtr); } destBitmap->Dispose(); } if (restoreColors) { ::SetBkColor(hdc, oldBkColor); ::SetTextColor(hdc, oldTextColor); } if (deleteDIBSection) { // This will get rid of the Bitmap and it's bits ::DeleteObject(hBitmap); GpFree(dibBmpInfo); } } // Also handles META_DIBSTRETCHBLT // There is not a usage parameter with these records -- the // usage is always DIB_RGB_COLORS. VOID WmfEnumState::DIBBitBlt( ) { DWORD rop = *((DWORD UNALIGNED *)RecordData); // If No-Op ROP, do nothing; just return if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000)) { return; } if (rop != SRCCOPY && rop != NOTSRCCOPY && rop != PATCOPY && rop != BLACKNESS && rop != WHITENESS) { RopUsed = TRUE; } INT paramIndex = 7; if (RecordType != WmfRecordTypeDIBBitBlt) { paramIndex = 9; // META_DIBSTRETCHBLT } INT dibIndex = paramIndex + 1; if (!IsSourceInRop3(rop)) { if (((GetCurrentRecordSize() / 2) - 3) == (GDIP_EMFPLUS_RECORD_TO_WMF(RecordType) >> 8)) { paramIndex++; } INT dstX = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); INT dstY = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); INT dstWidth = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); INT dstHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex]); // We know that this call will succeed because we have a 2K buffer // by default CreateRecordToModify(20); // For some strange reason we need to play the record on both // Win2K and Win9x. So create a WMF record for PatBlt and play it ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(WmfRecordTypePatBlt); ModifiedWmfRecord->rdSize = 10; ModifiedWmfRecord->rdParm[5] = (WORD) dstX; ModifiedWmfRecord->rdParm[4] = (WORD) dstY; ModifiedWmfRecord->rdParm[3] = (WORD) dstWidth; ModifiedWmfRecord->rdParm[2] = (WORD) dstHeight; if (rop != PATCOPY && SrcCopyOnly) { *((DWORD*) ModifiedWmfRecord->rdParm) = PATCOPY; } else { *((DWORD*) ModifiedWmfRecord->rdParm) = rop; } // Now play the record (below) } else { BITMAPINFOHEADER UNALIGNED * srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(((WORD *)RecordData) + dibIndex); UINT numPalEntries; UINT dibBitsSize; if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) && GetDibNumPalEntries(TRUE, srcDibInfo->biSize, srcDibInfo->biBitCount, srcDibInfo->biCompression, srcDibInfo->biClrUsed, &numPalEntries) && ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0)) { if ((srcDibInfo->biBitCount == 1) && (srcDibInfo->biPlanes == 1)) { DWORD UNALIGNED * rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo); if ((rgb[0] == 0x00000000) && (rgb[1] == 0x00FFFFFF)) { if (SrcCopyOnly && (rop != SRCCOPY) && CreateCopyOfCurrentRecord()) { *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY; goto PlayTheRecord; } else { // It is a compatible monochrome bitmap, which means it // will use the TextColor and BkColor, so no recoloring // is needed. Since we are not using SrcCopy that means // that it's a mask COLORREF oldBkColor = ::SetBkColor(Hdc, BkColor); COLORREF oldTextColor = ::SetTextColor(Hdc, TextColor); this->PlayRecord(); ::SetBkColor(Hdc, oldBkColor); ::SetTextColor(Hdc, oldTextColor); return; } } } UINT usage = DIB_RGB_COLORS; INT dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage); if (IsMetafile()) { if (dstDibSize > 0) { INT size = SIZEOF_METARECORDHEADER + (dibIndex * sizeof(WORD)) + dstDibSize; if (CreateRecordToModify(size)) { ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType); ModifiedWmfRecord->rdSize = size / 2; GpMemcpy(ModifiedWmfRecord->rdParm, RecordData, (dibIndex * sizeof(WORD))); ModifyDib(DIB_RGB_COLORS, srcDibInfo, NULL, (BITMAPINFOHEADER UNALIGNED *)(ModifiedWmfRecord->rdParm + dibIndex), numPalEntries, dibBitsSize, ColorAdjustTypeBitmap); } } goto PlayTheRecord; } // At this point, we are not going to play the record. We're // going to call StretchDIBits. One reason is because we want // to set the StretchBltMode, which is only used if a stretching // method is called. // Also, it avoids us doing an allocation/copy and then GDI doing // another allocation/copy (GDI has to do this to align the // DIB on a DWORD boundary). BITMAPINFOHEADER UNALIGNED * dstDibInfo = srcDibInfo; if (dstDibSize > 0) { if (CreateRecordToModify(dstDibSize)) { // ModifiedRecord is Aligned dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)ModifiedRecord; ModifyDib(DIB_RGB_COLORS, srcDibInfo, NULL, dstDibInfo, numPalEntries, dibBitsSize, ColorAdjustTypeBitmap); } } else if (!IsDwordAligned(dstDibInfo)) { // The srcDibInfo may not aligned properly, so we make // a copy of it, so that it will be aligned. dstDibSize = GetCurrentRecordSize() - (SIZEOF_METARECORDHEADER + (dibIndex * sizeof(WORD))); if (CreateRecordToModify(dstDibSize)) { dstDibInfo = (BITMAPINFOHEADER *)ModifiedRecord; GpMemcpy(dstDibInfo, srcDibInfo, dstDibSize); } } if (SrcCopyOnly) { rop = SRCCOPY; } INT srcX, srcY; INT dstX, dstY; INT srcWidth, srcHeight; INT dstWidth, dstHeight; dstX = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); dstY = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); dstWidth = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); dstHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); srcX = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); srcY = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); if (RecordType != WmfRecordTypeDIBBitBlt) { // META_DIBSTRETCHBLT srcWidth = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); srcHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex--]); } else { srcWidth = dstWidth; srcHeight = dstHeight; } // Need to flip the source y coordinate to call StretchDIBits. srcY = dstDibInfo->biHeight - srcY - srcHeight; OutputDIB(Hdc, NULL, dstX, dstY, dstWidth, dstHeight, srcX, srcY, srcWidth, srcHeight, dstDibInfo, NULL, DIB_RGB_COLORS, rop, TRUE); return; } } PlayTheRecord: this->PlayRecord(); } VOID WmfEnumState::StretchDIBits( ) { DWORD rop = *((DWORD UNALIGNED *)RecordData); // If No-Op ROP, do nothing; just return if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000)) { return; } if (rop != SRCCOPY && rop != NOTSRCCOPY && rop != PATCOPY && rop != BLACKNESS && rop != WHITENESS) { RopUsed = TRUE; } if (IsSourceInRop3(rop)) { if(((GetCurrentRecordSize() / 2) > (SIZEOF_METARECORDHEADER / sizeof(WORD)) + 11)) { BITMAPINFOHEADER UNALIGNED * srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(((WORD *)RecordData) + 11); UINT numPalEntries; UINT dibBitsSize; if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) && GetDibNumPalEntries(TRUE, srcDibInfo->biSize, srcDibInfo->biBitCount, srcDibInfo->biCompression, srcDibInfo->biClrUsed, &numPalEntries) && ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0)) { UINT usage = ((WORD *)RecordData)[2]; UINT oldUsage = usage; INT dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage); BITMAPINFOHEADER UNALIGNED * dstDibInfo = srcDibInfo; if (dstDibSize > 0) { if ((srcDibInfo->biBitCount == 1) && (srcDibInfo->biPlanes == 1)) { DWORD UNALIGNED * rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo); if ((rgb[0] == 0x00000000) && (rgb[1] == 0x00FFFFFF)) { if (SrcCopyOnly && (rop != SRCCOPY) && CreateCopyOfCurrentRecord()) { *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY; goto PlayTheRecord; } else { // It is a compatible monochrome bitmap, which means it // will use the TextColor and BkColor, so no recoloring // is needed. Since we are not using SrcCopy that means // that it's a mask COLORREF oldBkColor = ::SetBkColor(Hdc, BkColor); COLORREF oldTextColor = ::SetTextColor(Hdc, TextColor); this->PlayRecord(); ::SetBkColor(Hdc, oldBkColor); ::SetTextColor(Hdc, oldTextColor); return; } } } INT size = SIZEOF_METARECORDHEADER + (11 * sizeof(WORD)) + dstDibSize; if (CreateRecordToModify(size)) { ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType); ModifiedWmfRecord->rdSize = size / 2; GpMemcpy(ModifiedWmfRecord->rdParm, RecordData, (11 * sizeof(WORD))); // This will be aligned.... Do we want to take a chance? dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)(ModifiedWmfRecord->rdParm + 11); ModifyDib(oldUsage, srcDibInfo, NULL, dstDibInfo, numPalEntries, dibBitsSize, ColorAdjustTypeBitmap); } } if (!IsMetafile()) { if ((dstDibInfo == srcDibInfo) && (!IsDwordAligned(dstDibInfo))) { // The srcDibInfo may not aligned properly, so we make // a copy of it, so that it will be aligned. dstDibSize = GetCurrentRecordSize() - (SIZEOF_METARECORDHEADER + (11 * sizeof(WORD))); if (CreateRecordToModify(dstDibSize)) { dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)ModifiedRecord; GpMemcpy(dstDibInfo, srcDibInfo, dstDibSize); } } if (SrcCopyOnly) { rop = SRCCOPY; } INT dstX = (INT)((INT16)((WORD *)RecordData)[10]); INT dstY = (INT)((INT16)((WORD *)RecordData)[9]); INT dstWidth = (INT)((INT16)((WORD *)RecordData)[8]); INT dstHeight = (INT)((INT16)((WORD *)RecordData)[7]); INT srcX = (INT)((INT16)((WORD *)RecordData)[6]); INT srcY = (INT)((INT16)((WORD *)RecordData)[5]); INT srcWidth = (INT)((INT16)((WORD *)RecordData)[4]); INT srcHeight = (INT)((INT16)((WORD *)RecordData)[3]); OutputDIB(Hdc, NULL, dstX, dstY, dstWidth, dstHeight, srcX, srcY, srcWidth, srcHeight, dstDibInfo, NULL, usage, rop, TRUE); return; } } } } else // !IsSourceRop3 { if (rop != PATCOPY && SrcCopyOnly && CreateCopyOfCurrentRecord()) { *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = PATCOPY; } } PlayTheRecord: this->PlayRecord(); } BOOL WmfEnumState::CreateAndPlayOutputDIBRecord( HDC hdc, const RECTL * bounds, INT dstX, INT dstY, INT dstWidth, INT dstHeight, INT srcX, INT srcY, INT srcWidth, INT srcHeight, BITMAPINFOHEADER UNALIGNED * dibInfo, BYTE * bits, // if NULL, this is a packed DIB UINT usage, DWORD rop ) { INT bitsSize = GetDibBitsSize(dibInfo); UINT sizePalEntries; if (GetDibNumPalEntries(TRUE, dibInfo->biSize, dibInfo->biBitCount, dibInfo->biCompression, dibInfo->biClrUsed, &sizePalEntries)) { // We need to get the palette size that corresponds to the type // If we have a DIB_PAL_COLORS then each entry is 16bits sizePalEntries *= ((usage == DIB_PAL_COLORS)?2:sizeof(RGBQUAD)); } else { sizePalEntries = 0 ; } // We need at least a BITMAPINFO structure in there, but if there is a // palette, calculate the full size of the structure including the // palette INT bitmapHeaderSize = sizeof(BITMAPINFOHEADER) + sizePalEntries; INT size = SIZEOF_METARECORDHEADER + (11 * sizeof(WORD)) + bitmapHeaderSize + bitsSize ; // We cannot use the CreateRecordToModify because the record has already // been modified size = (size + 1) & ~1; METARECORD* metaRecord = (METARECORD*) GpMalloc(size); if (metaRecord != NULL) { metaRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(WmfRecordTypeStretchDIB); metaRecord->rdSize = size / 2; metaRecord->rdParm[10] = (WORD) dstX; metaRecord->rdParm[9] = (WORD) dstY; metaRecord->rdParm[8] = (WORD) dstWidth; metaRecord->rdParm[7] = (WORD) dstHeight; metaRecord->rdParm[6] = (WORD) srcX; metaRecord->rdParm[5] = (WORD) srcY; metaRecord->rdParm[4] = (WORD) srcWidth; metaRecord->rdParm[3] = (WORD) srcHeight; metaRecord->rdParm[2] = (WORD) usage; *(DWORD UNALIGNED *)(&(metaRecord->rdParm[0])) = rop; GpMemcpy((BYTE*)(&(metaRecord->rdParm[11])), dibInfo, bitmapHeaderSize); GpMemcpy((BYTE*)(&(metaRecord->rdParm[11])) + bitmapHeaderSize, bits, bitsSize); ::PlayMetaFileRecord(hdc, HandleTable, metaRecord, NumObjects); GpFree(metaRecord); return TRUE; } return FALSE; } VOID WmfEnumState::ModifyRecordColor( INT paramIndex, ColorAdjustType adjustType ) { COLORREF origColor = *((COLORREF UNALIGNED *)&(((WORD *)RecordData)[paramIndex])); COLORREF modifiedColor = ModifyColor(origColor, adjustType); if (modifiedColor != origColor) { if (CreateCopyOfCurrentRecord()) { *((COLORREF UNALIGNED *)&(ModifiedWmfRecord->rdParm[paramIndex])) = modifiedColor; } } } COLORREF MfEnumState::ModifyColor( COLORREF color, ColorAdjustType adjustType ) { if (AdjustType != ColorAdjustTypeDefault) { adjustType = AdjustType; } switch (color & 0xFF000000) { case 0x00000000: break; case 0x01000000: // Palette Index { PALETTEENTRY palEntry; if (::GetPaletteEntries(CurrentPalette, color & 0x000000FF, 1, &palEntry) == 1) { color = RGB(palEntry.peRed, palEntry.peGreen, palEntry.peBlue); } else { color = RGB(0, 0, 0); WARNING(("Failed to get palette entry")); } } break; case 0x02000000: // Palette RGB default: color &= 0x00FFFFFF; break; } // Possible perfomance improvement: recolor the SelectedPalette so only // RGB values need to be recolored here. if (Recolor != NULL) { Recolor->ColorAdjustCOLORREF(&color, adjustType); } // Palette RGB values don't get dithered (at least not on NT), so we // only want to make it a PaletteRGB value if it is a solid color in // the palette. if (Is8Bpp) { COLORREF matchingColor; matchingColor = (::GetNearestColor(Hdc, color | 0x02000000) & 0x00FFFFFF); // Pens and Text don't get Dithered so match them to the logical palette // the other adjustTypes do so they will get halftoned if ((matchingColor == color) || (adjustType == ColorAdjustTypePen) || (adjustType == ColorAdjustTypeText)) { return color | 0x02000000; } } return color; } BOOL MfEnumState::CreateRecordToModify( INT size ) { if (size <= 0) { size = this->GetCurrentRecordSize(); } // add a little padding to help insure we don't read past the end of the buffer size += 16; if (ModifiedRecordSize < size) { ASSERT(ModifiedRecordSize == 0); if (size <= GDIP_RECORDBUFFER_SIZE) { ModifiedRecord = RecordBuffer; } else if (size <= SizeAllocedRecord) { ModifiedRecord = AllocedRecord; } else { VOID * newRecord; INT allocSize; // alloc in increments of 1K allocSize = (size + 1023) & (~1023); ModifiedRecord = NULL; newRecord = GpRealloc(AllocedRecord, allocSize); if (newRecord != NULL) { ModifiedRecord = newRecord; AllocedRecord = newRecord; SizeAllocedRecord = allocSize; } } } else { ASSERT(ModifiedRecord != NULL); ASSERT(ModifiedRecordSize == size); } if (ModifiedRecord != NULL) { ModifiedRecordSize = size; return TRUE; } WARNING(("Failed to create ModifiedRecord")); return FALSE; } BOOL WmfEnumState::CreateCopyOfCurrentRecord() { if (ModifiedRecordSize > 0) { // We already made a modified record. Don't do it again. ASSERT(ModifiedRecord != NULL); return TRUE; } INT size = this->GetCurrentRecordSize(); if (CreateRecordToModify(size)) { METARECORD * modifiedRecord = (METARECORD *)ModifiedRecord; modifiedRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType); modifiedRecord->rdSize = size / 2; if (RecordDataSize > 0) { GpMemcpy(modifiedRecord->rdParm, RecordData, RecordDataSize); } return TRUE; } WARNING(("Failed to create copy of current record")); return FALSE; } VOID WmfEnumState::MakeSolidBlackBrush() { INT size = SIZEOF_METARECORDHEADER + sizeof(LOGBRUSH16); CreateRecordToModify(size); ModifiedWmfRecord->rdSize = size / 2; ModifiedWmfRecord->rdFunction = META_CREATEBRUSHINDIRECT; LOGBRUSH16 * logBrush = (LOGBRUSH16 *)(ModifiedWmfRecord->rdParm); logBrush->lbStyle = BS_SOLID; logBrush->lbColor = PALETTERGB(0,0,0); logBrush->lbHatch = 0; } VOID WmfEnumState::CalculateViewportMatrix() { GpRectF destViewport((REAL)DstViewportOrg.x, (REAL)DstViewportOrg.y, (REAL)DstViewportExt.cx, (REAL)DstViewportExt.cy); GpRectF srcViewport((REAL)ImgViewportOrg.x, (REAL)ImgViewportOrg.y, (REAL)ImgViewportExt.cx, (REAL)ImgViewportExt.cy); Status status = ViewportXForm.InferAffineMatrix(destViewport, srcViewport); if (status != Ok) { ViewportXForm.Reset(); } } VOID WmfEnumState::SetViewportOrg() { // If this is the first SetViewportOrg then we need to save that value and // calculate a transform from out viewport to this viewport ImgViewportOrg.x = (INT)((INT16)((WORD *)RecordData)[1]); ImgViewportOrg.y = (INT)((INT16)((WORD *)RecordData)[0]); if (FirstViewportOrg || FirstViewportExt) { FirstViewportOrg = FALSE; // If we have processed the first ViewportExt call then we can calculate // the transform from our current viewport to the new viewport if (!FirstViewportExt) { CalculateViewportMatrix(); } } else { // We need to keep the new Viewport origin to be able to calculate // the viewport bottom right corner before passing it through a // transform GpPointF newOrg((REAL) ImgViewportOrg.x, (REAL) ImgViewportOrg.y); // Transform the new viewport with our viewport transformation ViewportXForm.Transform(&newOrg); DstViewportOrg.x = GpRound(newOrg.X); DstViewportOrg.y = GpRound(newOrg.Y); if(CreateRecordToModify()) { ModifiedWmfRecord->rdFunction = CurrentWmfRecord->rdFunction; ModifiedWmfRecord->rdSize = CurrentWmfRecord->rdSize; ModifiedWmfRecord->rdParm[0] = (WORD)GpRound(newOrg.Y); ModifiedWmfRecord->rdParm[1] = (WORD)GpRound(newOrg.X); } this->PlayRecord(); } } VOID WmfEnumState::SetViewportExt() { // If this is the first SetViewportOrg then we need to save that value and // calculate a transform from out viewport to this viewport ImgViewportExt.cx = (INT)((INT16)((WORD *)RecordData)[1]); ImgViewportExt.cy = (INT)((INT16)((WORD *)RecordData)[0]); if (FirstViewportOrg || FirstViewportExt) { FirstViewportExt = FALSE; // If we have processed the first ViewportExt call then we can calculate // the transform from our current viewport to the new viewport if (!FirstViewportOrg) { CalculateViewportMatrix(); } } else { // We need to transform the point, so add the current origin // of the Viewport GpPointF newExt((REAL) ImgViewportExt.cx, (REAL) ImgViewportExt.cy); // Transform the new viewport with our viewport transformation ViewportXForm.VectorTransform(&newExt); if(CreateRecordToModify()) { ModifiedWmfRecord->rdFunction = CurrentWmfRecord->rdFunction; ModifiedWmfRecord->rdSize = CurrentWmfRecord->rdSize; ModifiedWmfRecord->rdParm[0] = (WORD)GpRound(newExt.Y); ModifiedWmfRecord->rdParm[1] = (WORD)GpRound(newExt.X); } this->PlayRecord(); } } VOID WmfEnumState::CreateRegion() { // Check if the region it too big when mapped to device space. if (!Globals::IsNt) { // There is a bug in Win9x GDI where the code which plays METACREATEREGION doesn't copy the // entire region data, it is off by 8 bytes. This seems to have been introduced to allow // for compatibility with an older header format, WIN2OBJECT. We get around this by increasing // the size of the record by 8 bytes. No other harm done. if (CreateCopyOfCurrentRecord()) { // When we create a copy of the record, we add 16 bytes of padding so we know this // won't overflow into other memory. ModifiedWmfRecord->rdSize += 4; } } this->PlayRecord(); } HFONT CreateTrueTypeFont( HFONT hFont ); VOID WmfEnumState::CreateFontIndirect( ) { LOGFONT16 * logFont = (LOGFONT16 *)RecordData; BOOL recordCopied = FALSE; if (!Globals::IsNt) { // We have a bug in Win9x that the OUT_TT_ONLY_PRECIS flag is // not always respected so if the font name is MS SANS SERIF // change it to Times New Roman // Since we don't have a string compare in ASCII do it in UNICODE WCHAR faceName[32]; if (AnsiToUnicodeStr((char*)(logFont->lfFaceName), faceName, sizeof(faceName)/sizeof(WCHAR)) && (UnicodeStringCompareCI(faceName, L"MS SANS SERIF") == 0)) { if (CreateCopyOfCurrentRecord()) { GpMemcpy(((LOGFONT16 *)(ModifiedWmfRecord->rdParm))->lfFaceName, "Times New Roman", sizeof("Times New Roman")); recordCopied = TRUE; } } } if (logFont->lfOutPrecision != OUT_TT_ONLY_PRECIS) { // Instruct GDI to use only True Type fonts, since bitmap fonts // are not scalable. if (recordCopied || CreateCopyOfCurrentRecord()) { ((LOGFONT16 *)(ModifiedWmfRecord->rdParm))->lfOutPrecision = OUT_TT_ONLY_PRECIS; } } this->PlayRecord(); } VOID WmfEnumState::SelectObject() { this->PlayRecord(); // In case we selected a region on Win9x, we need to intersect if (!Globals::IsNt) { DWORD index = CurrentWmfRecord->rdParm[0]; if (GetObjectTypeInternal((*HandleTable).objectHandle[index]) == OBJ_REGION) { this->IntersectDestRect(); } } } VOID WmfEnumState::IntersectDestRect() { if (!IsMetafile()) { POINT windowOrg; SIZE windowExt; ::SetWindowOrgEx(Hdc, DstViewportOrg.x, DstViewportOrg.y, &windowOrg); ::SetWindowExtEx(Hdc, DstViewportExt.cx, DstViewportExt.cy, &windowExt); // We are always in device units ::IntersectClipRect(Hdc, DestRectDevice.left, DestRectDevice.top, DestRectDevice.right, DestRectDevice.bottom); ::SetWindowOrgEx(Hdc, windowOrg.x, windowOrg.y, NULL); ::SetWindowExtEx(Hdc, windowExt.cx, windowExt.cy, NULL); } } VOID WmfEnumState::SetROP2() { INT rop = (INT)((INT16)(((WORD *)RecordData)[0])); if (rop != R2_BLACK && rop != R2_COPYPEN && rop != R2_NOTCOPYPEN && rop != R2_WHITE ) { RopUsed = TRUE; } this->PlayRecord(); } BOOL WmfEnumState::ProcessRecord( EmfPlusRecordType recordType, UINT recordDataSize, const BYTE * recordData ) { BOOL forceCallback = FALSE; MfFsmState nextState = MfFsmStart; if (IsFirstRecord) { // Bitmap fonts are not good for playing metafiles because they // don't scale well, so use a true type font instead as the default font. HFONT hFont = CreateTrueTypeFont((HFONT)GetCurrentObject(Hdc, OBJ_FONT)); if (hFont != NULL) { DefaultFont = hFont; ::SelectObject(Hdc, hFont); } IsFirstRecord = FALSE; } // See if we're doing enumeration for an external app if (ExternalEnumeration) { if (recordData == NULL) { recordDataSize = 0; } else if (recordDataSize == 0) { recordData = NULL; } // make sure it's an EMF enum type recordType = GDIP_WMF_RECORD_TO_EMFPLUS(recordType); // See if the app changed the record at all. if ((recordType != RecordType) || (recordDataSize != RecordDataSize) || ((recordDataSize > 0) && ((CurrentWmfRecord == NULL) || (recordData != (const BYTE *)CurrentWmfRecord->rdParm)))) { // Yes, we need to override what happened in StartRecord CurrentWmfRecord = NULL; RecordType = recordType; RecordData = recordData; RecordDataSize = recordDataSize; } } // Ignore all non-escape records if IgnorePostcript is TRUE if (recordType == WmfRecordTypeEscape || !IgnorePostscript) { GDIP_TRY switch (recordType) { // According to NT playback code, this is a EOF record, but it // is just skipped by the NT player. case GDIP_WMF_RECORD_TO_EMFPLUS(0x0000): // End of metafile record break; // These records are not played back (at least in Win2000). // Apparently they haven't been supported since before Win3.1! case WmfRecordTypeSetRelAbs: case WmfRecordTypeDrawText: case WmfRecordTypeResetDC: case WmfRecordTypeStartDoc: case WmfRecordTypeStartPage: case WmfRecordTypeEndPage: case WmfRecordTypeAbortDoc: case WmfRecordTypeEndDoc: case WmfRecordTypeCreateBrush: case WmfRecordTypeCreateBitmapIndirect: case WmfRecordTypeCreateBitmap: ONCE(WARNING1("Unsupported WMF record")); break; default: // unknown record -- ignore it WARNING1("Unknown WMF Record"); break; case WmfRecordTypeSetBkMode: case WmfRecordTypeSetMapMode: case WmfRecordTypeSetPolyFillMode: case WmfRecordTypeSetStretchBltMode: case WmfRecordTypeSetTextCharExtra: case WmfRecordTypeSetTextJustification: case WmfRecordTypeSetWindowOrg: case WmfRecordTypeSetWindowExt: case WmfRecordTypeOffsetWindowOrg: case WmfRecordTypeScaleWindowExt: case WmfRecordTypeOffsetViewportOrg: case WmfRecordTypeScaleViewportExt: case WmfRecordTypeLineTo: case WmfRecordTypeMoveTo: case WmfRecordTypeExcludeClipRect: case WmfRecordTypeIntersectClipRect: case WmfRecordTypeArc: case WmfRecordTypeEllipse: case WmfRecordTypePie: case WmfRecordTypeRoundRect: case WmfRecordTypePatBlt: case WmfRecordTypeTextOut: case WmfRecordTypePolygon: case WmfRecordTypePolyline: case WmfRecordTypeFillRegion: case WmfRecordTypeFrameRegion: case WmfRecordTypeInvertRegion: case WmfRecordTypePaintRegion: case WmfRecordTypeSetTextAlign: case WmfRecordTypeChord: case WmfRecordTypeSetMapperFlags: case WmfRecordTypeExtTextOut: case WmfRecordTypeAnimatePalette: case WmfRecordTypeSetPalEntries: case WmfRecordTypeResizePalette: case WmfRecordTypeSetLayout: case WmfRecordTypeDeleteObject: case WmfRecordTypeCreatePalette: // Play the current record. // Even if it fails, we keep playing the rest of the metafile. // There is a case that GdiComment with EPS may fail. this->PlayRecord(); break; case WmfRecordTypeCreateRegion: this->CreateRegion(); break; case WmfRecordTypeCreateFontIndirect: this->CreateFontIndirect(); break; case WmfRecordTypeSetBkColor: this->SetBkColor(); break; case WmfRecordTypeSetTextColor: this->SetTextColor(); break; case WmfRecordTypeFloodFill: this->FloodFill(); break; case WmfRecordTypeExtFloodFill: this->ExtFloodFill(); break; case WmfRecordTypeSaveDC: this->SaveHdc(); // plays the record break; case WmfRecordTypeSetPixel: this->SetPixel(); break; case WmfRecordTypeDIBCreatePatternBrush: this->DibCreatePatternBrush(); break; case WmfRecordTypeCreatePatternBrush: // Obsolete but still played back this->CreatePatternBrush(); break; case WmfRecordTypeCreatePenIndirect: this->CreatePenIndirect(); if (FsmState == MfFsmSelectBrush) { nextState = MfFsmCreatePenIndirect; } break; case WmfRecordTypeCreateBrushIndirect: this->CreateBrushIndirect(); if (FsmState == MfFsmPSData) { nextState = MfFsmCreateBrushIndirect; } break; case WmfRecordTypeSelectObject: // What if we break out of the FSM, we do want to Create the appropriate // brush and pens right?! if (FsmState == MfFsmCreateBrushIndirect) { nextState = MfFsmSelectBrush; break; } else if (FsmState == MfFsmSelectBrush || FsmState == MfFsmCreatePenIndirect) { nextState = MfFsmSelectPen; break; } this->SelectObject(); break; case WmfRecordTypeRectangle: this->Rectangle(); break; case WmfRecordTypeSetROP2: { INT rdParm = (INT)((INT16)(((WORD *)RecordData)[0])); if (FsmState == MfFsmSelectPen && (INT)(rdParm == R2_NOP)) { nextState = MfFsmSetROP; } this->SetROP2(); } break; case WmfRecordTypeBitBlt: // Obsolete but still played back this->BitBlt(); forceCallback = TRUE; break; case WmfRecordTypeStretchBlt: // Obsolete but still played back this->StretchBlt(); forceCallback = TRUE; break; case WmfRecordTypeEscape: { INT escapeCode = (INT)((INT16)(((WORD *)RecordData)[0])); this->Escape(); // optionally plays the record if (FsmState == MfFsmStart && escapeCode == POSTSCRIPT_DATA && Globals::IsNt && IsPostscriptPrinter() && GdiCentricMode && SoftekFilter) { nextState = MfFsmPSData; } // Comments do not change the current state if (escapeCode == MFCOMMENT) { nextState = FsmState; } } break; case WmfRecordTypeRestoreDC: this->RestoreHdc(); // optionally plays the record break; case WmfRecordTypeSetDIBToDev: this->SetDIBitsToDevice(); forceCallback = TRUE; break; case WmfRecordTypeSelectPalette: // We don't select in any palettes when playing the metafile, // because we don't want to invalidate our halftoning palette. // Keep track of the palette so we can map from PALETTEINDEXes // to RGB values. this->SelectPalette((INT)((INT16)(((WORD *)recordData)[0]))); break; case WmfRecordTypeRealizePalette: // We don't want to invalidate our halftoning palette by realizing one // from a metafile. break; case WmfRecordTypePolyPolygon: this->PolyPolygon(); break; case WmfRecordTypeDIBBitBlt: this->DIBBitBlt(); forceCallback = TRUE; break; case WmfRecordTypeDIBStretchBlt: this->DIBStretchBlt(); forceCallback = TRUE; break; case WmfRecordTypeStretchDIB: this->StretchDIBits(); forceCallback = TRUE; break; case WmfRecordTypeSetViewportOrg: this->SetViewportOrg(); break; case WmfRecordTypeSetViewportExt: this->SetViewportExt(); break; case WmfRecordTypeSelectClipRegion: case WmfRecordTypeOffsetClipRgn: this->PlayRecord(); if (!Globals::IsNt) { this->IntersectDestRect(); } break; } GDIP_CATCH forceCallback = TRUE; GDIP_ENDCATCH } FsmState = nextState; this->EndRecord(); return forceCallback; }