Я уже писал о создании документов в формате PDF. Он представляет собой контейнер, который состоит из объектов различного типа: текста, изображений, шрифтов и так далее. Иногда может потребоваться операция обратная созданию – разобрать этот контейнер на составные части. Сегодня я рассмотрю извлечение изображений из PDF файлов и сохранение их на диск.
Добавленные в документ формата PDF изображения хранятся как отдельные объекты. Это позволяет рисовать их на страницах документа сколько угодно раз без дальнейшего увеличения размера файла. Сначала рассмотрим функцию, которая извлекает из PDF-файла эти объекты. Для этого я воспользуюсь библиотекой Foxit Quick PDF Library (бывшая Debenu Quick PDF Library).
Извлечем изображения из документа по страницам: и сопоставим результаты работы этих функций:
Добавленные в документ формата PDF изображения хранятся как отдельные объекты. Это позволяет рисовать их на страницах документа сколько угодно раз без дальнейшего увеличения размера файла. Сначала рассмотрим функцию, которая извлекает из PDF-файла эти объекты. Для этого я воспользуюсь библиотекой Foxit Quick PDF Library (бывшая Debenu Quick PDF Library).
procedure ExtractImages(const sFileName, sPath: String); var pdf: TDebenuPDFLibrary; iImageCount, iImageIndex, iImageID, iImageWidth, iImageHeight: Integer; sImageExt, sImageFileName: String; begin pdf := TDebenuPDFLibrary.Create; try if pdf.LoadFromFile(sFileName, '') = 1 then begin pdf.SetFindImagesMode(2); iImageCount := pdf.FindImages; for iImageIndex := 1 to iImageCount do begin // Идентификатор изображения iImageID := pdf.GetImageID(iImageIndex); if pdf.SelectImage(iImageID) = 1 then begin // Тип изображения case pdf.ImageType of 1: sImageExt := 'jpg'; 2: sImageExt := 'bmp'; 3: sImageExt := 'tif'; 4: sImageExt := 'png'; else sImageExt := 'Unknown'; end; // Размер изображения iImageWidth := pdf.ImageWidth; iImageHeight := pdf.ImageHeight; sImageFileName := ExpandFileName( Format('%s%s ObjNo=%d %dx%d.%s', [sPath, iImageIndex.ToString.PadLeft (iImageCount.ToString.Length, '0'), pdf.ImageObjectNumber, iImageWidth, iImageHeight, sImageExt])); WriteLn(sImageFileName); pdf.SaveImageToFile(sImageFileName); end; end; end else WriteLn('File open error') finally pdf.Free; end; end;В исходном коде этой функции я отмечу два момента. Во-первых, вызов функции SetFindImagesMode, которая устанавливает режим поиска изображений в словаре ресурсов для функции FindImages. Он может принимать следующие значения:
- 1 = "Default search" – рекурсивный поиск изображений только в ресурсах страниц и аннотаций в документе. Это самый быстрый метод и требует меньше всего памяти, но неиспользуемые изображения не будут найдены.
- 2 = "Full search" – исследуется каждый объект в документе. Это занимает больше времени и требует больше памяти, но будут найдены все изображения, даже если они не используются ни одной из страниц или аннотаций.
- 3 = "Default search, full convert" – отличается от режима 1 тем, что изображение конвертируется в bmp-формат.
- 4 = "Full search, full convert" – отличается от режима 2 тем, что изображение конвертируется в bmp-формат.
procedure ExtractImages(const sFileName, sPath: String); var pdf: TDebenuPDFLibrary; iPageCount, iPageIndex, iImageListID, iImageCount, iImageIndex, iImageWidth, iImageHeight, iImageBitsPerPixel, iImageObjectNumber: Integer; sColorSpaceType, sImageExt, sImageFileName: String; begin pdf := TDebenuPDFLibrary.Create; try if pdf.LoadFromFile(sFileName, '') = 1 then begin iPageCount := pdf.PageCount; for iPageIndex := 1 to iPageCount do begin pdf.SelectPage(iPageIndex); // Список изображений на странице iImageListID := pdf.GetPageImageList(0); if iImageListID <> 0 then try iImageCount := pdf.GetImageListCount(iImageListID); for iImageIndex := 1 to iImageCount do begin // Тип изображения case pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 400) of 1: sImageExt := 'jpg'; 2: sImageExt := 'bmp'; 3: sImageExt := 'tif'; 4: sImageExt := 'png'; else sImageExt := 'Unknown'; end; // Размер изображения iImageWidth := pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 401); iImageHeight := pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 402); // Глубина цвета iImageBitsPerPixel := pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 403); // Тип цветового пространства case pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 404) of 1: sColorSpaceType := 'Gray'; 2: sColorSpaceType := 'RGB'; 3: sColorSpaceType := 'CMYK'; else sColorSpaceType := '-'; end; // Номер объекта изображения iImageObjectNumber := pdf.GetImageListItemIntProperty(iImageListID, iImageIndex, 407); sImageFileName := ExpandFileName( Format('%spage#%s img#%s ObjNo=%d %s bpp=%d %dx%d.%s', [sPath, iPageIndex.ToString.PadLeft (iPageCount.ToString.Length, '0'), iImageIndex.ToString.PadLeft (iImageCount.ToString.Length, '0'), iImageObjectNumber, sColorSpaceType, iImageBitsPerPixel, iImageWidth, iImageHeight, sImageExt])); WriteLn(sImageFileName); pdf.SaveImageListItemDataToFile(iImageListID, iImageIndex, 0, sImageFileName); end; finally pdf.ReleaseImageList(iImageListID); end; end; end else WriteLn('File open error') finally pdf.Free; end; end;Она обрабатывает документ по страницам. Для каждой страницы составляется список нарисованных на ней изображений, а затем они извлекаются и сохраняются на диск. Обратите внимание на свойство номер 407. Это номер объекта изображения, который я получил в первом варианте функции с помощью ImageObjectNumber. По этому номеру можно сгруппировать дублирующиеся изображения или сопоставить с результатами работы первой функции. Кроме использованных мной целочисленных свойств (GetImageListItemIntProperty) у элементов списка изображений есть еще вещественные свойства (GetImageListItemDblProperty). В текущей версии библиотеки вещественные свойства имеют ID от 501 до 508 и позволяют получить доступ к координатам изображения на странице.
Извлечем изображения из документа по страницам: и сопоставим результаты работы этих функций:
- первая извлекла из документа 169 изображений, а вторая по ссылкам на страницах 220 изображений;
- изображение "002 ObjNo=43 1454x1122.jpg" в документе ни где не используется и только увеличивает его размер;
- изображение "001 ObjNo=37 2559x1629.jpg" найдено на 7-й странице документа "page#07 img#1 ObjNo=37 RGB bpp=24 2559x1629.jpg";
- изображение "ObjNo=222" является хорошим примером повторного использования объектов в документе. Только на 17-й странице оно используется 5 раз.
procedure ExtractImages(const sFileName, sPath: String); var pdf: TDebenuPDFLibrary; iFileHandle, iPageCount, iPageIndex, iPageRef, iImageListID, iImageCount, iImageIndex, iImageWidth, iImageHeight, iImageBitsPerPixel, iImageObjectNumber: Integer; sColorSpaceType, sImageExt, sImageFileName: String; begin pdf := TDebenuPDFLibrary.Create; try iFileHandle := pdf.DAOpenFileReadOnly(sFileName, ''); if iFileHandle <> 0 then begin iPageCount := pdf.DAGetPageCount(iFileHandle); for iPageIndex := 1 to iPageCount do begin iPageRef := pdf.DAFindPage(iFileHandle, iPageIndex); if iPageRef <> 0 then begin // Список изображений на странице iImageListID := pdf.DAGetPageImageList(iFileHandle, iPageRef); if iImageListID <> 0 then try iImageCount := pdf.DAGetImageListCount(iFileHandle, iImageListID); for iImageIndex := 1 to iImageCount do begin // Тип изображения case pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 400) of 1: sImageExt := 'jpg'; 2: sImageExt := 'bmp'; 3: sImageExt := 'tif'; 4: sImageExt := 'png'; else sImageExt := 'Unknown'; end; // Размер изображения iImageWidth := pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 401); iImageHeight := pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 402); // Глубина цвета iImageBitsPerPixel := pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 403); // Тип цветового пространства case pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 404) of 1: sColorSpaceType := 'Gray'; 2: sColorSpaceType := 'RGB'; 3: sColorSpaceType := 'CMYK'; else sColorSpaceType := '-'; end; // Номер объекта изображения iImageObjectNumber := pdf.DAGetImageIntProperty(iFileHandle, iImageListID, iImageIndex, 407); sImageFileName := ExpandFileName( Format('%spage#%s img#%s ObjNo=%d %s bpp=%d %dx%d.%s', [sPath, iPageIndex.ToString.PadLeft (iPageCount.ToString.Length, '0'), iImageIndex.ToString.PadLeft (iImageCount.ToString.Length, '0'), iImageObjectNumber, sColorSpaceType, iImageBitsPerPixel, iImageWidth, iImageHeight, sImageExt])); WriteLn(sImageFileName); pdf.DASaveImageDataToFile(iFileHandle, iImageListID, iImageIndex, sImageFileName); end; finally pdf.DAReleaseImageList(iFileHandle, iImageListID); end; end; end; end else WriteLn('File open error') finally pdf.Free; end; end;Эта функция работает аналогично предыдущей, только немного быстрее. Но есть небольшая проблема. Метод DAGetImageIntProperty не умеет получать номер объекта изображения (свойство номер 407 у GetImageListItemIntProperty). Значит эту возможность реализуем сами. Аналогично ImageObjectNumber, для этого нам необходим доступ к приватным членам класса. Что бы не изменять исходный код библиотеки, я сделал небольшой class helper:
unit ahQuickPDF; interface uses DebenuPDFLibrary1811; type TQuickPDFHelper = class helper for TQuickPDF function ImageObjectNumber: Integer; function DAGetImageIntProperty(const FileHandle, ImageListID, ImageIndex, PropertyID: Integer): Integer; end; implementation uses DebenuPDFLibrarySmartAccess, DebenuPDFLibraryStructure, DebenuPDFLibraryImageList; { TQuickPDFHelper } function TQuickPDFHelper.ImageObjectNumber: Integer; begin with Self do if Assigned(FSelImage) then Result := FSelImage.IndRef.ObjNum else Result := 0; end; function TQuickPDFHelper.DAGetImageIntProperty (const FileHandle, ImageListID, ImageIndex, PropertyID: Integer): Integer; var Link: TSmartPDFLink; ImageList: TPDFImageList; begin Result := 0; with Self do begin Link := ValidFileHandle(FileHandle); if Assigned(Link) then begin ImageList := ValidImageListID(Link, ImageListID); if Assigned(ImageList) then if PropertyID = 407 then begin var ImgRef: TPDFIndRef := ImageList.ImageRef(ImageIndex); if Assigned(ImgRef) then Result := ImgRef.ObjNum; end else Result := Link.Doc.GetImageIntProperty(ImageList, ImageIndex, PropertyID); end; end; end; end.
Комментариев нет:
Отправить комментарий