Я уже писал о создании документов в формате 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.

Комментариев нет:
Отправить комментарий