C++ 获取 word、excel中拷贝的图片

目标

C++ 从剪贴板中获取在 word、excel中拷贝图片,然后再其他软件中显示该图片,一般bitmap,jpeg,png 是我们常用的渲染格式。所以获取的图片肯能需要转换成常用的格式才能显示

分析

在 word中拷贝图片,在剪贴板中可能存在几种格式如下

CF_DIB,CF_BITMAP

在docx文件中拷贝一张图可能是这种格式

CF_DIB,CF_BITMAP 这类型的图片获取最简单

::GetClipboardData(format) 获取对应的bmp图片就可以了。

HTML Format

在doc文件拷贝多张图是这种格式

我们看一个样例中的html 数据:

<p class=MsoNormal ><img width="554"  height="268"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps26.png" ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><span><span style="position:absolute;z-index:1;margin-left:0.0000px;
margin-top:0.0000px;width:32.0000px;height:32.0000px;" ><img width="32"  height="32"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps27.png" ></span></span><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p></o:p></span></p><p class=MsoNormal ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><img width="555"  height="341"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps28.jpg" ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><img width="554"  height="310"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps29.jpg" ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><img width="554"  height="173"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps30.jpg" ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p><p class=MsoNormal ><img width="555"  height="305"  src="file:///C:\Users\28748\AppData\Local\Temp\ksohtml23516\wps31.jpg" ><span style="mso-spacerun:'yes';font-family:等线;font-size:10.5000pt;
mso-font-kerning:1.0000pt;" ><o:p>&nbsp;</o:p></span></p>

发现都是标签的形式,然后图片就是以本地文件路径放在标签src地址上,我们扫描信息获取标签可以获取图片地址,从而在其他软件中加载显示即可

CF_ENHMETAFILE

在doc文件拷贝一张图可能是这种格式,Metafile 分为普通图元文件和增强型图元文件两种,扩展名分别为.wmf和.emf。图元文件将图形定义为编码的线段和图形,也称作“绘图类型”的图形。
我们看一下 metafile 的说明

Metafile和位图的关系,就像点阵图和位元映射图形(矢量图形)的关系一样。点阵图通常来自实际的图像,而metafile则大多是通过电脑程式人为建立的。点阵是通过记录像素点的位置描绘图形,而矢量图形是通过数学公式即时演算画出的图形。Metafile由一系列与图形函式呼叫相同的二进位记录组成,这些记录一般用于绘制直线、曲线、填入的区域和文字等。

这篇文章目的就是获取这种格式的图片并且转换为其他格式

获取剪贴板中 CF_ENHMETAFILE 即 metafile 图片

直接上代码


//打开剪贴板
::OpenClipboard(GetDesktopWindow());
if (::IsClipboardFormatAvailable(CF_ENHMETAFILE))
{
    //获取CF_ENHMETAFILE 格式图片
    HENHMETAFILE hemf = (HENHMETAFILE)::GetClipboardData(CF_ENHMETAFILE);
    HENHMETAFILE  hMetaFile=CopyEnhMetaFile(hemf, "c:\\test.emf");//保存到文件
    //关闭CMetafileDC并获得它的句柄
    DeleteEnhMetaFile(hMetaFile);
}
::CloseClipboard();

绘制 metafile 图片


//打开剪贴板
::OpenClipboard(GetDesktopWindow());
if (::IsClipboardFormatAvailable(CF_ENHMETAFILE))
{
    //获取CF_ENHMETAFILE 格式图片
    HENHMETAFILE hemf = (HENHMETAFILE)::GetClipboardData(CF_ENHMETAFILE);
    HDC dc = ::GetDC(NULL);
    PlayEnhMetaFile(memDC, hemf, &rect); //绘制函数
}
::CloseClipboard();

读取 metafile 图片


HENHMETAFILE hemf; 
hemf = GetEnhMetaFile("c:\\test.emf");
ENHMETAHEADER   emh; 
// Get the header from the enhanced metafile.   
ZeroMemory( &emh, sizeof(ENHMETAHEADER) );   
emh.nSize = sizeof(ENHMETAHEADER);   
if( GetEnhMetaFileHeader( hemf, sizeof( ENHMETAHEADER ), &emh ) == 0 )   
{   
    DeleteEnhMetaFile( hemf );   
    return FALSE;   
}

metafile 文件的头结构体如下


typedef struct tagENHMETAHEADER {
  DWORD iType;
  DWORD nSize;
  RECTL rclBounds;
  RECTL rclFrame;
  DWORD dSignature;
  DWORD nVersion;
  DWORD nBytes;
  DWORD nRecords;
  WORD  nHandles;
  WORD  sReserved;
  DWORD nDescription;
  DWORD offDescription;
  DWORD nPalEntries;
  SIZEL szlDevice;
  SIZEL szlMillimeters;
  DWORD cbPixelFormat;
  DWORD offPixelFormat;
  DWORD bOpenGL;
  SIZEL szlMicrometers;
} ENHMETAHEADER, *PENHMETAHEADER, *LPENHMETAHEADER;

具体每个元素的具体含义可以参考 MSDN

我们需要用到的是 rclFrame 这个参数获取图片的大小

metafile 转 bitmap

::OpenClipboard(GetDesktopWindow());
if (::IsClipboardFormatAvailable(CF_ENHMETAFILE))
{
    HENHMETAFILE hemf = (HENHMETAFILE)::GetClipboardData(CF_ENHMETAFILE);
    HBITMAP     bitmap;
    ENHMETAHEADER head;
    HDC         memDC;
    memset(&head, 0, sizeof(ENHMETAHEADER));
    head.nSize = sizeof(ENHMETAHEADER);

    GetEnhMetaFileHeader(hemf, sizeof(ENHMETAHEADER), &head);
    HDC dc = ::GetDC(NULL);
    RECT    rect;


    rect.left = 0;
    rect.top = 0;
    rect.right = head.rclFrame.right;
    rect.bottom = head.rclFrame.bottom;

    memDC = ::CreateCompatibleDC(dc);
    //Create a bitmap compatible to Window DC   
    bitmap = ::CreateCompatibleBitmap(dc, lWidth, lHeight);

    ::SelectObject(memDC, bitmap);

    //这里用白色的画刷,word中的透明图片会显示黑色

    SetBackColorToWhite(memDC);

    //把metafile 图片绘制到bitmap 上,绘制完成之后 bitmap就是我们需要的
    PlayEnhMetaFile(memDC, hemf, &rect);

}

bitmap 转 png buffer

bool save_png_memory(HBITMAP hbitmap, std::vector<BYTE> &data)
{
    Gdiplus::Bitmap bmp(hbitmap, nullptr);

    //write to IStream
    IStream* istream = nullptr;
    CreateStreamOnHGlobal(NULL, TRUE, &istream);

    CLSID clsid_png;
    CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);
    Gdiplus::Status status = bmp.Save(istream, &clsid_png);
    if (status != Gdiplus::Status::Ok)
        return false;

    //get memory handle associated with istream
    HGLOBAL hg = NULL;
    GetHGlobalFromStream(istream, &hg);

    //copy IStream to buffer
    int bufsize = GlobalSize(hg);
    data.resize(bufsize);

    //lock & unlock memory
    LPVOID pimage = GlobalLock(hg);
    memcpy(&data[0], pimage, bufsize);
    GlobalUnlock(hg);

    istream->Release();
    return true;
}

调用代码

std::vector<BYTE> data;
if (save_png_memory(bitmap, data))
{
    //write from memory to file for testing:
    std::ofstream fout("test.png", std::ios::binary);
    fout.write((char*)data.data(), data.size());
    fout.close();
}

总结

把上面的功能串起来之后 metafile 文件可以直接在内存中转化为bitmap,也可以转换成png、jpeg等需要的格式,不需要先保存到本地,减少io


   转载规则


《C++ 获取 word、excel中拷贝的图片》 Smoking 采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
 上一篇
inno setup 拷贝文件夹 移动文件夹 inno setup 拷贝文件夹 移动文件夹
用代码实现文件夹的递归拷贝和移动,不用setup 中字段实现
2019-07-11
下一篇 
《深阅读:信息爆炸时代我们如何读书》-(日)斋藤孝 《深阅读:信息爆炸时代我们如何读书》-(日)斋藤孝
关于本书作者开篇就强调我们需要读书的重要性,读书能获得安静,自处,见识和成长等,读书这个技能需要不断的锻炼和练习的,如何快速读书,如何快速消化,如何快速应用,皆是自我阅读一定量之后形成一套自己的方法,也能自己去识别哪些是好书,那些书可以快速
2019-07-01
  目录