يعد عرض صفحة PDF إلى JPEG عمليتين يميل الأشخاص إلى تشغيلهما معًا ثم تصحيح أخطائهما بشكل منفصل. أولاً، تقوم بتحويل الصفحة إلى صورة نقطية بكسل بالدقة التي تختارها. ثم تقوم بتسليم تلك الصورة النقطية إلى مشفر JPEG واختيار الجودة. يمتلك PDFium Component النصف الأول من خلال RenderPage؛ والنصف الثاني هو VCL عادي، TJPEGImage من Vcl.Imaging.jpeg. التماس بينهما هو المكان الذي تعيش فيه القرارات المثيرة للاهتمام، لأن الدقة التي تختارها من جانب العرض والجودة التي تختارها من جانب التشفير تتبادلان ضد بعضهما البعض وضد حجم الملف بطرق يسهل الخطأ فيها
الشيء الذي يجب استيعابه قبل أي تعليمات برمجية: لا تحتوي صفحة PDF على وحدات بكسل. يتم وصفه بالنقاط، حيث النقطة الواحدة هي 1/72 بوصة، والصفحة عبارة عن رسم متجهي يتم قياسه بتلك النقاط. عندما تطلب من PDFium العرض، فإنك تختار عدد وحدات البكسل التي سيتم إبراز هذا الرسم عليها، وهذا الاختيار هو DPI. إذا فهمت الحساب بشكل خاطئ، فستقوم إما بعرض صورة مصغرة ضبابية عندما كنت تريد نسخة أصلية للطباعة، أو تقوم بتخصيص صورة نقطية بدقة 200 ميجابكسل لشيء مقدر له أن يكون معاينة بدقة 120 بكسل
من DPI إلى أبعاد البكسل
يريد RenderPage أرقام صحيحة لبكسل Width و Height، وليس DPI. لذا فإن المهمة الأولى هي التحويل. تبلغ الصفحة عن حجمها بالنقاط من خلال PageWidth و PageHeight (كلاهما Double)، والتحويل هو نفسه الذي يستخدمه كل محول نقطي: تساوي وحدات البكسل عدد النقاط مضروبًا في DPI المستهدف مقسومًا على 72. تبلغ صفحة US Letter 612 × 792 نقطة. عند 150 نقطة لكل بوصة، يصبح ذلك 1275 في 1650 بكسل؛ عند 72 نقطة لكل بوصة، تظل 612 × 792، بكسل واحد لكل نقطة، وهي الحالة التي ينسى الناس أنها مجرد هوية
// Pdf.PageNumber must already point at the page you want.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... use Bitmap ...
Bitmap.Free; // the function-form RenderPage hands you ownership
يقرر تفصيلان في تلك الأسطر الأربعة ما إذا كانت التعليمات البرمجية صحيحة أم لا. الأول هو أن شكل الدالة لـ RenderPage يُرجع TBitmap أنت تمتلكه. قام PDFium بتخصيصه ومشى بعيدًا؛ إذا لم تقم بـ Free في كل تكرار، فإن دفعة تزيد عن بضع مئات من الصفحات تسرب بضع مئات من الصور النقطية وتتضخم العملية حتى ينهار شيء ما. الثاني هو وسيطة Color، clWhite هنا. عادةً ما يتم رسم صفحات PDF على افتراض وجود ركيزة بيضاء غير شفافة، وتؤدي الصفحة ذات الشفافية المعروضة على لون الخلفية الخاطئ إلى حواف موحلة أو هالات داكنة ضالة. الأبيض هو الإعداد الافتراضي الصحيح لكل مستند تقريبًا؛ توجد المعلمة للحالة النادرة التي لا يكون فيها الأمر كذلك
0, 0 هي الإزاحات Left و Top إلى الصفحة، في مساحة الإحداثيات المتدرجة، وتتركها عند الصفر ما لم تقم بالاقتطاع. إن ro0 هي الدوران: اتركها عند الصفر وسيحترم PDFium أي دوران تعلنه الصفحة بالفعل في إدخال /Rotate الخاص بها، بحيث تظهر الصفحة المؤلفة بشكل أفقي كأفقي دون أن تفعل أي شيء
تشفير الصورة النقطية كـ JPEG
بمجرد وجود الصورة النقطية، يصبح JPEG هو الجزء السهل، وهو Delphi خالص. يقوم TJPEGImage.Assign بنسخ الصورة النقطية، ويحدد CompressionQuality الجودة على مقياس من 1 إلى 100، ويكتب SaveToFile الملف. قاعدة الترتيب الوحيدة هي أنه يجب تعيين الجودة قبل الحفظ، لأنها تحكم التشفير الذي يشغله SaveToFile
uses
Vcl.Graphics, Vcl.Imaging.jpeg, PDFium;
procedure SavePageAsJpeg(Pdf: TPdf; PageNumber, Dpi, Quality: Integer;
const FileName: string);
var
Bitmap: TBitmap;
Jpeg: TJPEGImage;
begin
Pdf.PageNumber := PageNumber;
Bitmap := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Dpi / 72),
Round(Pdf.PageHeight * Dpi / 72),
ro0, [], clWhite);
try
Jpeg := TJPEGImage.Create;
try
Jpeg.Assign(Bitmap);
Jpeg.CompressionQuality := Quality; // 1..100
Jpeg.SaveToFile(FileName);
finally
Jpeg.Free;
end;
finally
Bitmap.Free;
end;
end;
تبدو تعليمة try/finally المتداخلة هذه مزعجة لمساعد من صفحة واحدة، لكنها صحيحة تمامًا لدفعة واحدة. تحرر الكتلة الداخلية المشفر، وتحرر الكتلة الخارجية الصورة النقطية، وأي منهما ينطلق عند استثناء لا يزال يحرر ما يملكه. قم بطيها في واحدة ويمكن أن يؤدي استثناء أثناء التشفير إلى إعاقة الصورة النقطية. على المدى الطويل، هذا هو الفرق بين المحول الذي ينتهي والمحول الذي يموت في الصفحة 300 بملف تالف ومربع حوار نفاد الذاكرة
اختيار DPI والجودة معًا
الزران ليسا مستقلين عن الغرض من الإخراج، والخطأ الشائع هو رفع كليهما بدافع الحذر. تعد الصورة المصغرة للويب المعروضة بدقة 300 نقطة لكل بوصة والمحفوظة بجودة 95 مئات الكيلوبايت تتظاهر بأنها صورة 120 بكسل؛ يرمي المتصفح كل شيء تقريبًا على النطاق الصغير. قم بمطابقة الدقة بالبكسل التي يحتاجها الإخراج بالفعل، ثم اختر جودة تنجو من الضغط الفاقد لـ JPEG دون آثار مرئية
| الإخراج | DPI | جودة JPEG |
|---|---|---|
| صورة مصغرة للقائمة | 72 | 60-70 |
| معاينة على الشاشة | 96-150 | 80-85 |
| عرض عالي التفاصيل | 200-300 | 85-95 |
| نسخة مطبوعة أصلية | 300-600 | 90-100 |
تستحق جودة JPEG كلمة تحذير في حد ذاتها. إنه ليس قرصًا خطيًا. تقفز القفزة من 70 إلى 85 تحسينًا بصريًا حقيقيًا لنمو متواضع للملفات؛ تضاعف القفزة من 95 إلى 100 حجم الملف تقريبًا لفرق لا يكاد يراه أحد، لأن الجودة 100 لا تزال غير خالية من الفقدان، فهي تتوقف فقط عن التخلص من الكثير. بالنسبة للصفحات المليئة بالنصوص، يعمل الضغط القائم على الكتل في JPEG على تلطيخ الحواف الحادة للصور الرمزية إلى رنين خافت، ولهذا السبب فإن الجودة التي تقل عن حوالي 80 تجعل النص يبدو ممسوحًا ضوئيًا على ما يجب أن يكون ناتجًا واضحًا. إذا كانت الصفحات عبارة عن نصوص في الغالب ويمكنك تغيير التنسيقات، فإن PNG يعرض هذا النص بدون الرنين؛ يكسب JPEG مكانه في المحتوى الفوتوغرافي والمختلط حيث يكون ضغطه أصغر حقًا
صور مصغرة أصغر وأسرع
عندما يكون الهدف صورة مصغرة بدلاً من استنساخ دقيق، يمكنك إخبار العارض بالقيام بعمل أقل. تأخذ المعلمة Options مجموعة من علامات TRenderOption، ويقايض عدد قليل منها الدقة بالسرعة بالطريقة التي تريدها المعاينة الصغيرة تمامًا. يسقط reGrayscale اللون، والذي يعرض بشكل أسرع وينتج صورة نقطية أصغر لتشفيرها. يتخطى reNoSmoothImage و reNoSmoothPath صقل الحواف وهو أمر غير مرئي على نطاق الصورة المصغرة على أي حال
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Fit the page inside MaxW x MaxH while preserving aspect ratio.
Scale := Min(MaxW / Pdf.PageWidth, MaxH / Pdf.PageHeight);
Result := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Scale),
Round(Pdf.PageHeight * Scale),
ro0, [reGrayscale, reNoSmoothImage], clWhite);
end;
تعرض حالة الصورة المصغرة أيضًا الطريقة الأنظف للتفكير في الحجم. بدلاً من المرور عبر DPI، احسب عامل مقياس واحد يناسب الصفحة داخل مربع الإحاطة ويحافظ على نسبة العرض إلى الارتفاع، وهو ما يفعله Min للنسبتين. ينتهي الأمر بكل من الصفحة العمودية والصفحة الأفقية داخل نفس المربع دون تشويه، ولا يتعين عليك أبدًا أن تفكر في عدد النقاط لكل بوصة الذي يتوافق مع "ملاءمة في 200 في 280". محذار واحد مع reGrayscale: فهو يحول محتوى الصورة النقطية إلى اللون الرمادي، لكن عمليات التعبئة بالمتجهات والنصوص تحافظ على قيم ألوانها في المحرك، لذا فإن الصفحة التي تتكون في الغالب من فن المتجهات قد تعود أقل أحادية اللون مما يوحي به اسم العلامة. للحصول على نتيجة كاملة التدرج الرمادي حقًا، فإن تحويل الصورة النقطية المعروضة باستخدام GrayscalePdfBitmap هو المسار الموثوق
معالجة مستند كامل دفعة واحدة
يعد تجميعها معًا لمستند كامل بمثابة حلقة على PageCount، مع نقل PageNumber صفحة واحدة في كل مرة. تستند الصفحات إلى 1: الصفحة الأولى هي PageNumber := 1، وتعمل الحلقة إلى PageCount شاملة، وليس PageCount - 1. الشيء الآخر الذي يجب أن تحترمه الدفعة هو عقد التحميل الصامت. لا يثير تعيين Active := True أي رسالة أبدًا على ملف تالف أو كلمة مرور خاطئة؛ إنه يترك فقط Active عند False. تحقق منه قبل عرض صفحة واحدة، أو يعمل RenderPage الأول مقابل مستند لم يتم فتحه أبدًا
procedure ExportAllPages(const PdfPath, OutDir: string; Dpi, Quality: Integer);
var
Pdf: TPdf;
I, Digits: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := PdfPath;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.Create('Could not open ' + PdfPath);
Digits := Length(IntToStr(Pdf.PageCount)); // zero-pad so files sort right
for I := 1 to Pdf.PageCount do
SavePageAsJpeg(Pdf, I, Dpi, Quality,
Format('%s\page_%.*d.jpg', [OutDir, Digits, I]));
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
الحشو الصفري عبر Digits هو شيء صغير يوفر عليك قضاء فترة بعد الظهر لاحقًا. قم بتسمية الملفات page_1.jpg إلى page_10.jpg وأي أداة تقوم بفرزها كسلاسل تضع page_10 مباشرة بعد page_1، مما يؤدي إلى تشويش الترتيب. الحشو لعرض أعلى رقم صفحة، بحيث ينتج مستند مكون من 300 صفحة page_001.jpg، يحافظ على الترتيب المعجمي وترتيب الصفحة متطابقين في كل مكان لاحقًا
بالنسبة للمستندات الكبيرة بما يكفي بحيث يستغرق التحويل وقتًا ملحوظًا، قم بتشغيله بعيدًا عن مؤشر ترابط واجهة المستخدم أو ضخ الرسائل بين الصفحات بحيث يظل التطبيق مستجيبًا، وامنح المستخدم طريقة للإيقاف. إذا كنت تعرض صفحات كبيرة جدًا وتريد الإلغاء الذي يعض في منتصف الصفحة بدلاً من أن يكون بين الصفحات فقط، فإن PDFium Component يحتوي على مسار عرض تدريجي مع رمز الإلغاء؛ هذه آلية أثقل مما تحتاجه معظم صادرات الدفعات، ولكنها موجودة عندما تكون صفحة واحدة بدقة 600 نقطة لكل بوصة بطيئة بما يكفي لعرقلة العمل
هناك اقتران أخير يستحق المعرفة. يؤدي تحويل الصفحة إلى نقطية إلى تجاهل طبقة النص الخاصة بها: صورة JPEG عبارة عن بكسلات، والكلمات الموجودة فيها لم تعد قابلة للتحديد أو البحث. عندما تحتاج إلى كل من صورة والنص الأساسي، قم بالعرض للصورة واسحب النص بشكل منفصل، وهو ما تغطيه المقالة المصاحبة حول استخراج النص من مستندات PDF باستخدام PDFium Component. أوفرلود RenderPage وخيارات العرض الموضحة هنا هي جزء من PDFium Component لـ Delphi و C++Builder