Office系列(2)---提取Office文件(Word、PPT)中的所有图片

回顾一下上文结尾的问题:如何给文档设置一个合适的封面图?其中一个解决方案就是,获取Office文件内部的图片作为封面。这里就详细介绍下获取图片的几种方式,以及他们各自的优缺点。
PS:因为之前用VSTO开发过PPT的插件程序,所以对PPT的COM ApI比较熟悉,所以下面的样例和代码都以操作PPT文档为主,Word、PPT、Excel之间的结构差异还是很大的,详细的文档描述还是要去官网查看(传送门)。

基于Office的解决方案

通过Office COM API打开PPT文档,遍历每个幻灯片(Slide)的每个形状(Shape),然后通过剪切板将包含图片的形状复制到内存中,再保存到本地目录。

/// <summary>
/// 导出PPT文件中图片到目标文件夹下
/// </summary>
/// <param name="sourcePath">PPT文件路径</param>
/// <param name="targetDir">目标文件夹</param>
public static void GetPPTImages(string sourcePath, string targetDir)
{
    var app = new PowerPoint.Application();
    var persentation = app.Presentations.Open(sourcePath, WithWindow: MsoTriState.msoFalse);
    int num = 1;
    for (int i = 1; i <= persentation.Slides.Count; i++)
    {
        var slide = persentation.Slides[i];
        for (int j = 1; j <= slide.Shapes.Count; j++)
        {
            var shape = slide.Shapes[j];
            if (shape.Type == MsoShapeType.msoPicture || shape.Type == MsoShapeType.msoLinkedPicture || shape.Fill.Type == MsoFillType.msoFillPicture)
            {
                shape.Copy();//shape自带的方法,复制到剪切板中
                if (Clipboard.ContainsImage())
                {
                    var imgPath = Path.Combine(targetDir, num + ".jpg");
                    Clipboard.GetImage().Save(imgPath, System.Drawing.Imaging.ImageFormat.Jpeg);
                    num++;
                }
            }
        }
    }
    persentation.Close();
}

上面代码中有几个需要注意的地方:

  1. SlidesShapes都是从1开始遍历的
  2. Clipboard对象在System.Windows.Forms.dll
  3. PPT文件中图片有两种存在的方式,单独为形状的图片(shape.Type == MsoShapeType.msoPicture)和作为形状背景的图片(shape.Fill.Type == MsoFillType.msoFillPicture)
  4. 使用shape.Copy()来复制形状到剪切板,而不能直接通过Clipboard.SetDataObject(shape)

简单介绍下上面代码的实现思路,就好像用Office软件打开了PPT文件,然后选择包含图片的形状,Ctrl + C然后Ctrl + V到本地。有兴趣的同学可以尝试下,在PPT中复制图片,然后在微信对话框中粘贴。

当然这种做法有着很大的缺陷:

  1. 图片不全,通过上面的判定方式获取到的只是一部分的图片并没有把所有文件都保存到本地
  2. 图片异常,如果尝试过,将PPT中图片复制到微信里,你会发现它是把整个形状生成了一张图片。比如在一个文本框中输入文字,然后再加上图片背景,最后复制到出来的就是包含了文字的一张图片。
  3. 图片模糊,复制出来的图片分辨率有限,不再是原来的图片分辨率了。

总而言之,该解决方案仅供学习参考,实际应用还是不合适的!!!

基于OpenXml的解决方案

Office Open XML 是由Microsoft开发的一种以XML为基础并以ZIP格式压缩的电子文件规范,支持文件、表格、备忘录、幻灯片等文件格式。

简单来说一个PPT文件(.pptx后缀),其实是一个ZIP格式压缩的电子文件,压缩文件内通过XML标记了文档的内容,比如,引用的图片、文字的排列方式等等。
常用的几种Office文件中的,Word文件有.doc.docx两种后缀,PowerPoint文件有.ppt.pptx两种后缀,Excel文件有.xls.xlsx两种后缀。这其实就是文件版本的差异。 OpenXml也只能用在2007及以后的文件版本中(后缀为.docx.pptx.xlsx)。

测试:准备同一PPT文件分别另存为.ppt.pptx两个版本,直接修改文件后缀为.zip

PS:图片资源存放路径 /ppt/media/


代码(目前只有获取PPT和Word文件图片的,Excel的暂时未考虑):
先通过Nuget包管理安装需要用到的包

DocumentFormat.OpenXml

using DocumentFormat.OpenXml.Packaging;

/// <summary>
/// 导出PPT文件中所有图片
/// </summary>
/// <param name="sourcePath">源文件路径</param>
/// <param name="targetDir">目标文件存放目录</param>
/// <returns></returns>
public static void ExportPPTImages(string sourcePath,string targetDir)
{
    using (PresentationDocument presentationDocument = PresentationDocument.Open(sourcePath, isEditable: false))
    {
        PresentationPart presentationPart = presentationDocument.PresentationPart;
        DocumentFormat.OpenXml.Presentation.Presentation presentation = presentationPart.Presentation;
        List<ImagePart> list = new List<ImagePart>();
        foreach (DocumentFormat.OpenXml.Presentation.SlideId item in presentation.SlideIdList.OfType<DocumentFormat.OpenXml.Presentation.SlideId>())
        {
            SlidePart slidePart = presentationPart.GetPartById(item.RelationshipId) as SlidePart;
            list.AddRange(slidePart.ImageParts);
        }
        List<IGrouping<string, ImagePart>> list2 = list.GroupBy(d => d.Uri.OriginalString).ToList();

        //导出PPT所有的图片
        for (int i = 0; i < list2.Count; i++)
        {
            ImagePart imagePart = list2[i].FirstOrDefault();
            string tempFileName = Path.Combine(targetDir, $"image_{i}.jpg");
            using (Stream stream = imagePart.GetStream(FileMode.Open))
            {
                using (Bitmap bitmap = new Bitmap(stream))
                {
                    bitmap.Save(tempFileName, System.Drawing.Imaging.ImageFormat.Jpeg);
                }
            }
        }
        //presentation.Save();
    }
}

/// <summary>
/// 导出Word文件中所有图片
/// </summary>
/// <param name="sourcePath">源文件路径</param>
/// <param name="targetDir">目标文件存放目录</param>
/// <returns></returns>
public static void ExportWordImages(string sourcePath,string targetDir)
{
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(sourcePath, isEditable: false))
    {
        var list2 = wordDocument.MainDocumentPart.ImageParts.GroupBy(d => d.Uri.OriginalString).ToList();
        for (int i = 0; i < list2.Count; i++)
        {
            ImagePart imagePart = list2[i].FirstOrDefault();
            string tempFileName = Path.Combine(targetDir, $"image_{i}.jpg");
            using (Stream stream = imagePart.GetStream(FileMode.Open))
            {
                using (Bitmap bitmap = new Bitmap(stream))
                {
                    bitmap.Save(tempFileName, System.Drawing.Imaging.ImageFormat.Jpeg);
                }
            }
        }
    }
}

可以通过OpenXml获取到Office XML的抽象类型,当然也可以对内容进行编辑啦~有兴趣的可以去微软OpenXml官网了解下,这里就不过多介绍了。
综上所述,这个解决方案还是很靠谱的,可以直接用于生产环境。还有缺陷就是无法处理2003版本的Office文件,这个也只能通过转换文件为新版本再来处理了。

基于第三方插件的解决方案

好吧,第三方插件又来了,对没错说的就是你Spire。关于插件的介绍都已经写在上一篇文章中了,这里也不啰嗦了,直接上代码(这里只是做个引子,记录下PPT文件的代码,其他的自己去官网找Demo吧)。

using Spire.Presentation;

/// <summary>
/// 导出PPT文件中所有图片
/// </summary>
/// <param name="sourcePath">源文件路径</param>
/// <param name="targetDir">目标文件存放目录</param>
/// <returns></returns>
public static void ExportPPTImages2(string sourcePath, string targetDir)
{
    using (Presentation pres = new Presentation())
    {
        pres.LoadFromFile(sourcePath);
        for (int i = 0; i < pres.Images.Count; i++)
        {
            Image image = pres.Images[i].Image;
            string tempFileName = Path.Combine(targetDir, $"image_{i}.jpg");
            image.Save(tempFileName);
        }
    }
}

偷偷的说:用Spire正式版插件导出来的图片没有水印,可以放心使用~

总结

上面已经介绍了Office 2007及之后版本的文件其实是.zip格式的压缩文件,将所有图片提取出来后发现,一个100M的PPT文件,居然藏了600M的图片,有点意思啊!思来想去感觉一个100M的文件还是太大,那么在不影响效果的情况下,是不是可以调整处理下文件中的图片大小,来达到压缩整个文件大小的目的呢?下篇再来细细描述吧~~~

posted @ 2020-01-19 10:14  傅小灰  阅读(1566)  评论(0编辑  收藏  举报