1. 程式人生 > >Office系列(2)---提取Office檔案(Word、PPT)中的所有圖片

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.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的檔案還是太大,那麼在不影響效果的情況下,是不是可以調整處理下檔案中的圖片大小,來達到壓縮整個檔案大小的目的呢?下篇再來細細描述吧