回顧一下上文結尾的問題:如何給文檔設置一個合適的封面圖?其中一個解決方案就是,獲取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();
}
上面代碼中有幾個需要注意的地方:
Slides
和Shapes
都是從1開始遍歷的Clipboard
對象在System.Windows.Forms.dll
中- PPT文件中圖片有兩種存在的方式,單獨為形狀的圖片(
shape.Type == MsoShapeType.msoPicture
)和作為形狀背景的圖片(shape.Fill.Type == MsoFillType.msoFillPicture
) - 使用
shape.Copy()
來復制形狀到剪切板,而不能直接通過Clipboard.SetDataObject(shape)
。
簡單介紹下上面代碼的實現思路,就好像用Office軟件打開了PPT文件,然后選擇包含圖片的形狀,Ctrl + C
然后Ctrl + V
到本地。有興趣的同學可以嘗試下,在PPT中復制圖片,然后在微信對話框中粘貼。
當然這種做法有着很大的缺陷:
- 圖片不全,通過上面的判定方式獲取到的只是一部分的圖片並沒有把所有文件都保存到本地
- 圖片異常,如果嘗試過,將PPT中圖片復制到微信里,你會發現它是把整個形狀生成了一張圖片。比如在一個文本框中輸入文字,然后再加上圖片背景,最后復制到出來的就是包含了文字的一張圖片。
- 圖片模糊,復制出來的圖片分辨率有限,不再是原來的圖片分辨率了。
總而言之,該解決方案僅供學習參考,實際應用還是不合適的!!!
基於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的文件還是太大,那么在不影響效果的情況下,是不是可以調整處理下文件中的圖片大小,來達到壓縮整個文件大小的目的呢?下篇再來細細描述吧~~~