PDF lưu trữ hình ảnh như các đối tượng hạng nhất bên trong các luồng nội dung của nó. Khi một trang tham chiếu đến một bức ảnh, một bản quét hoặc một sơ đồ, dữ liệu pixel nằm trong một từ điển XObject cùng với hình học của trang. PDFium Component thể hiện điều đó thông qua hai thuộc tính trên TPdf: BitmapCount, trả về số lượng bitmap được nhúng trên trang hiện tại và Bitmap[Index], giải mã một trong số chúng thành một TBitmap mà bạn sở hữu và phải giải phóng. Đó là toàn bộ mô hình trích xuất. Vòng lặp bao gồm bốn dòng; điều cần cân nhắc là các thiết lập xung quanh
Mở tài liệu
Điều đầu tiên cần biết về TPdf là Active := True không bao giờ ném ra ngoại lệ. Lỗi tải, sai mật khẩu, tệp bị hỏng: tất cả chúng đều được nuốt trọn ở bên trong và thành phần đơn giản là vẫn ở trạng thái không hoạt động. Bạn phải tự kiểm tra cờ sau khi gán, nếu không bạn sẽ tiếp tục vào vòng lặp trang với PageCount trả về không và tự hỏi tại sao không có gì được trích xuất
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
Writeln('Failed to open: ', Pdf.FileName);
Exit;
end;
Writeln(Pdf.PageCount, ' pages');
// proceed to extraction
finally
Pdf.Free;
end;
end;
Các tệp được bảo vệ bằng mật khẩu tuân theo cùng một kiểu: gán Pdf.Password trước khi đặt Active := True. Nếu mật khẩu sai, Active vẫn là False và bạn không nhận được ngoại lệ nào để bắt. Trong một công cụ xử lý hàng loạt hàng trăm tệp, hành vi im lặng đó thực sự hữu ích: bạn tích lũy các lỗi trong một danh sách thay vì phải gỡ cuộn ngăn xếp lệnh gọi cho từng lỗi một
Lặp qua các trang và trích xuất bitmap
BitmapCount tính theo từng trang, vì vậy bạn phải thiết lập Pdf.PageNumber trước khi đọc nó. Số trang bắt đầu từ 1; mặc định là 0, nghĩa là không có trang nào được tải. Thuộc tính Bitmap[Index] bắt đầu từ 0 và trả về một TBitmap thuộc sở hữu của người gọi. Bạn phải giải phóng nó. Bỏ qua việc giải phóng bên trong một vòng lặp dài trên một tài liệu lớn và bộ nhớ sẽ tăng nhanh, bởi vì mỗi bitmap có thể là vài megabyte dữ liệu pixel thô trước bất kỳ quá trình nén nào
procedure ExtractAllImages(Pdf: TPdf; const OutputDir: string);
var
Page, Idx: Integer;
Bmp: TBitmap;
OutPath: string;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
for Idx := 0 to Pdf.BitmapCount - 1 do
begin
Bmp := Pdf.Bitmap[Idx];
if not Assigned(Bmp) then
Continue;
try
OutPath := Format('%s\p%d_img%d.bmp', [OutputDir, Page, Idx + 1]);
Bmp.SaveToFile(OutPath);
finally
Bmp.Free;
end;
end;
end;
end;
Việc bảo vệ bằng Assigned là rất quan trọng. Một số ít trình tạo PDF viết các XObject hình ảnh với kích thước pixel bằng không hoặc dữ liệu bị định dạng sai; trong những trường hợp đó, thành phần trả về nil thay vì một bitmap trống. Coi một giá trị trả về nil là một lỗi và dừng việc trích xuất là một phản xạ sai lầm: hãy bỏ qua nó, ghi lại trang và chỉ mục nếu bạn cần dấu vết kiểm toán, và tiếp tục. Phần còn lại của trang vẫn có thể tạo ra các hình ảnh hợp lệ
Lưu ý rằng vòng lặp bên ngoài thiết lập Pdf.PageNumber trên mỗi lần lặp. Việc gán đó là những gì tải trang vào trạng thái bên trong của thành phần và làm cho BitmapCount có ý nghĩa. Bỏ qua nó và bạn sẽ đọc đi đọc lại số lượng của cùng một trang. Mẫu này có vẻ thừa khi bạn viết nó, nhưng đó là cách API được thiết kế: trang là một con trỏ, không phải là một bộ sưu tập
Chọn định dạng đầu ra
BMP là không mất dữ liệu và luôn có sẵn mà không cần các unit bổ sung, điều này làm cho nó trở thành một mặc định tốt khi bạn chưa biết hình ảnh chứa gì. Khi kích thước tệp là vấn đề, định dạng pixel của TBitmap được trả về cho bạn biết codec nào là phù hợp. Một bitmap 32-bit mang một kênh alpha; PNG bảo tồn điều đó mà không mất dữ liệu. Một hình ảnh 24-bit lớn với tông màu liên tục là một ứng cử viên cho JPEG. Các hình ảnh nhỏ hơn hoặc những hình ảnh được vẽ với bảng màu hạn chế thường được để tốt hơn dưới dạng BMP thay vì chạy qua JPEG, điều này sẽ thêm các tạo tác khối ở các cài đặt chất lượng thấp và tiết kiệm được ít ở các cài đặt cao
procedure SaveBitmap(Bmp: TBitmap; const FileName: string);
var
Jpg: TJPEGImage;
begin
case UpperCase(ExtractFileExt(FileName)) of
'.JPG', '.JPEG':
begin
Jpg := TJPEGImage.Create;
try
Jpg.Assign(Bmp);
Jpg.CompressionQuality := 85;
Jpg.SaveToFile(FileName);
finally
Jpg.Free;
end;
end;
else
Bmp.SaveToFile(FileName); // BMP: lossless, no extra units
end;
end;
Trong thực tế, việc lựa chọn định dạng được thúc đẩy bởi Bmp.PixelFormat và kích thước. Nếu PixelFormat = pf32bit bạn cần một định dạng mang alpha; PNG là sự lựa chọn hiển nhiên, mặc dù nó yêu cầu unit PNGImage trong các phiên bản Delphi cũ. Đối với hình ảnh 24-bit rộng hơn khoảng 300 pixel, JPEG ở chất lượng 85 mang lại mức giảm kích thước ba phần một so với BMP mà không có sự suy giảm nhận thấy trong hầu hết các nội dung nhiếp ảnh. Dưới ngưỡng đó BMP có kích thước tương đương và hoàn toàn tránh được bất kỳ quyết định chất lượng nào
BitmapCount đếm và không đếm những gì
PDF phân biệt giữa các XObject hình ảnh và đồ họa vector được vẽ bằng các toán tử đường dẫn. Một trang trông phức tạp về mặt hình ảnh có thể trả về một BitmapCount bằng không nếu mọi thành phần đều là vector. Các trang được quét hầu như luôn trả về đúng một: máy quét viết toàn bộ bản quét thành một XObject hình ảnh toàn trang duy nhất ở bất kỳ độ phân giải nào máy quét được thiết lập. Các trang trộn văn bản sắp chữ với các bức ảnh nhúng trả về một mục nhập cho mỗi bức ảnh. Các đường kẻ trang trí, hình nền được tô bóng và viền bảng thường hoàn toàn không xuất hiện trong số lượng bitmap
Số lượng cũng không bao gồm các hình ảnh nội tuyến (inline images), một cấu trúc PDF hiếm khi được sử dụng nơi dữ liệu hình ảnh được nhúng trực tiếp vào luồng nội dung trang thay vì như một XObject có tên. Những cái đó nằm ngoài những gì API này thể hiện; chúng đủ hiếm trong các tài liệu thực tế đến mức hầu hết các công cụ trích xuất đơn giản là không xử lý chúng
Một chi tiết đáng ghi nhớ: BitmapCount mà bạn đọc là dành cho trang hiện tại kể từ lần gán PageNumber cuối cùng. Nếu mã của bạn rẽ nhánh hoặc gọi bất kỳ hàm nào làm thay đổi PageNumber giữa lúc đếm và lấy, bạn có thể đọc ít hình ảnh hơn không gian bạn đã cấp phát, hoặc lập chỉ mục vượt quá phần cuối. Hãy giữ việc đọc số lượng và vòng lặp Bitmap[] trên cùng một trang mà không chạm vào PageNumber ở giữa
Sử dụng TPdfView trong một ứng dụng biểu mẫu
Thành phần TPdfView phơi bày cùng các thuộc tính BitmapCount và Bitmap[], nhưng trang mà nó đọc là trang hiện đang được hiển thị của chế độ xem, không phải TPdf.PageNumber. Hai con trỏ trang này là độc lập; việc thiết lập một cái không làm di chuyển cái kia. Trong một ứng dụng biểu mẫu VCL có trình xem trực tiếp, bạn có thể gọi Pdf.PageNumber := N để điều khiển việc trích xuất thông qua TPdf trong khi trình xem vẫn ở bất cứ nội dung nào người dùng đã cuộn tới lần cuối. Sự tách biệt đó là có chủ ý và giữ cho trạng thái hiển thị của trình xem sạch sẽ trong khi một quá trình trích xuất nền chạy
Bộ nhớ và hiệu suất trong các tác vụ hàng loạt
Trên một kho lưu trữ lớn, ngân sách bộ nhớ là điều chính cần xem xét. Mỗi lệnh gọi Bitmap[] cấp phát một TBitmap mới trên heap, và trên một trang được quét 300 DPI thì dễ dàng lên tới 25 MB dữ liệu pixel thô trước bất kỳ quá trình mã hóa nào. Nếu bạn xử lý các trang trong một vòng lặp hẹp mà không giải phóng giữa các lần lặp, bộ làm việc sẽ tăng tuyến tính với số lượng hình ảnh. Hình dáng đúng luôn là: lấy một bitmap, làm những gì bạn cần, giải phóng nó, lấy cái tiếp theo. Nếu bạn cần giữ các tham chiếu đến một số bitmap cùng một lúc cho một bước so sánh, hãy đếm chúng trước bằng BitmapCount và cấp phát bộ chứa của bạn cho phù hợp, sau đó giải phóng từng cái ngay khi bạn hoàn thành việc với nó thay vì trì hoãn cho đến khi dọn dẹp cuối tài liệu. Trên một tài liệu có 500 trang được quét, sự khác biệt đó có thể có nghĩa là sự khác biệt giữa 25 MB và 12 GB RSS đỉnh
Các thuộc tính BitmapCount và Bitmap[] hiển thị ở đây là một phần của PDFium Component dành cho Delphi và C++Builder