پردازش تصویر در NodeJs با Jimp — راهنمای کاربردی

۱۸۸ بازدید
آخرین به‌روزرسانی: ۲۸ شهریور ۱۴۰۲
زمان مطالعه: ۸ دقیقه
پردازش تصویر در NodeJs با Jimp — راهنمای کاربردی

تصور کنید یک مشتری از شما خواسته است که همه تصاویر موجود در گالری یک وب‌سایت را واترمارک کنید و همچنین آن‌ها را برش داده و به وضوح (Resolution) یکسانی درآورید. در واقع راه‌حل شما باید با زیرساخت موجود جاوا اسکریپت کاربر ادغام شود. این کار از طریق یک بسته ساده اما قدرتمند NodeJs که اخیراً معرفی شده و Jimp نام دارد میسر خواهد بود.

Jimp اختصاری برای «برنامه دستکاری تصویر جاوا اسکریپت» (JavaScript Image Manipulation Program) است. می‌توانید صفحه معرفی پروژه را در این نشانی (+) ملاحظه کنید. استفاده از Jimp در یک مسیر صعودی قرار دارد و در حال حاضر بیش از 180،000 دانلود هفتگی دارد. تیم توسعه‌دهنده آن، issue-های مطرح شده روی گیت‌هاب را به سرعت شناسایی و رفع می‌کنند. در مواردی برخی از مشکلات در کمتر از 24 ساعت رفع شده و در طی 48 ساعت issue-ها بسته شده و برای انتشار در نسخه بعدی کامیت می‌شوند.

تعداد issue-های باز Jimp در حال حاضر در حدود 40 مورد است که برای یک پروژه به این بزرگی که زمان زیادی نیز از معرفی آن نمی‌گذرد، عدد بسیار مناسبی محسوب می‌شود. بدیهی است که جامعه بسیار فعالی در پس توسعه Jimp قرار دارند و از این رو سرمایه‌گذاری روی آن معقول است.

بسته Jimp را در پروژه خود با استفاده از دستور زیر نصب کنید:

npm i jimp

پیش از آن که به بحث توسعه Jimp بپردازیم، باید مطمئن شویم که این بسته از انواع تصاویری که قصد داریم استفاده کنیم پشتیبانی می‌کند. Jimp یک بسته دستکاری تصاویر «نقشه‌بیتی» (bitmap) است و از این رو در حال حاضر از تصاویر SVG یا مبتنی بر بردار پشتیبانی نمی‌کند. این حرف به آن معنی است که باید قالب‌های فونت معمول یعنی otf. و ttf. را به فایل‌های فونت نقشه‌بیتی یعنی fnt. تبدل کنیم. در این خصوص در ادامه بیشتر توضیح خواهیم داد.

بنابراین قالب‌های فایل مورد پشتیبانی Jimp به شرح زیر هستند:

  • jimp/jpeg@
  • jimp/png@
  • jimp/bmp@
  • jimp/tiff@
  • jimp/gif@

کار با تصاویر در NodeJS

Jimp را می‌توان مستقیماً در یک اسکریپت نود (node) سمت سرور ایمپورت کرد. می‌توانیم یک گردش دستور مبتنی بر promise را مورد استفاده قرار دهیم که هر زمان امکان دستکاری یک شیء را می‌دهد. بدین ترتیب ویرایش‌های مورد نیاز را روی تصویر خود مرحله به مرحله اجرا می‌کنیم و در نهایت تصور نهایی را با استفاده از تابع ()Jimp.write ذخیره می‌کنیم.

استفاده از کتابخانه Jimp در واقع روشی عالی برای تمرین promise-ها است، چون هر وظیفه را می‌توان به چند افزونه به صورت ()then تقسیم کرد. این فرایند را در ادامه تشریح خواهیم کرد.

فرایند تقسیم مراحل ویرایش تصویر به صورت زیر است:

  1. ابتدا یک تصویر قالب/مبنا را می‌خوانیم تا در یک دایرکتوری raw با آن کار کنیم.
  2. تصویر را به فایل تصویر فعال (active) دیگری کلون می‌کنیم که دستکاری‌های خود را روی آن اجرا خواهیم کرد.
  3. تصویر کلون شده را می‌خوانیم و آن را آماده دستکاری می‌سازیم.
  4. یک لوگوی واترمارک را برای قرار دادن روی تصویر بارگذاری می‌کنیم.
  5. یک فایل فونت را برای درج متن روی تصویر بارگذاری می‌کنیم.
  6. تصویر نهایی را در دایرکتوری export، اکسپورت می‌کنیم.

پیش از بررسی اسکریپت اجرا کننده وظایف فوق، باید برخی ملاحظات که هنگام کار پردازش تصویر مورد نیاز است را اجرا کنیم.

ملاحظه 1: ساختار پوشه

در زمان اجرای وظایفی مانند پردازش تصویر ابتدا باید مطمئن شویم که فایل‌های تصویر اصلی را بازنویسی نمی‌کنیم. به همین جهت، در ساختار پروژه خود دست‌کم باید سه پوشه داشته باشیم:

project_folder/
   raw/
       image1.jpg
       image2.jpg
       ...
   active/
   export/
   generate_image.js

یک رویه کاملاً گویا اما ضروری این است که تصاویر خام، فعال و تصاویر نهایی اکسپورت شده را در پوشه‌های مختلفی به ترتیب به صورت active ،raw و export ذخیره کنیم.

ملاحظه 2: کتابخانه‌های تصویر خارجی

این احتمال هست که مشتری شما فایل‌های تصویر را در سرورهای خود برای دستکاری شما آماده نداشته باشد. ممکن است تصاویر روی یک سرویس خارجی، مانند Dropbox ،Google Drive یا Amazon AWS ذخیره شده باشند. البته این وضعیت مشکلی نیست و در واقع موجب صرفه‌جویی در زحمت ما برای جدا کردن فایل‌های خام از فایل‌های فعال و اکسپورت شده می‌شود. شما می‌توانید با مراجعه به صفحه «توسعه‌دهندگان» این سرویس‌ها با روش کار آشنا شوید:

  • Dropbox API: (+)
  • Google Drive API: (+)
  • Amazon AWS / S3: (+) (به بخش SDKs اسکرول کنید.)

البته می‌توانید تصاویر اکسپورت شده‌تان را نیز به این سرویس‌ها انتقال دهید. اگر برای نمونه در سرویسی مانند آمازون S3 داده‌های بایتی تصویر را ارسال می‌کنید در این صورت نیاز نیست که تصاویر روی HTTP در دسترس عموم باشند. اما در حالتی که بخواهید یک تصویر را از یک URL به URL دیگر کپی کنید، در این صوت باید تصویر شما از طریق یک نشانی HTTP در دسترس باشد و از این رو باید ملاحظه 3 را که در ادامه آمده است بررسی کنید.

ملاحظه 3: پوشه استاتیک عمومی با NodeJS Express

Express امکان تعیین برخی پوشه‌ها به صورت عمومی را بسیار آسان‌تر ساخته است.

اگر بخواهیم همه تصاویر اکسپورت شده را درون یک پوشه استاتیک ذخیره کنیم، ابتدا باید آن را به روشی مانند زیر درون دایرکتوری ریشه (root) قرار دهید.

app/
   public
   static
   routes
   views
   app.js
   ...

سپس باید فایل app.js را ویرایش کنیم تا یک مسیر (route) برای این پوشه بسازیم:

1app.use('/exported-images', express.static('static'));

نیازی نیست که از همین نام پوشه استفاده کنید و می‌توانید هر URL که دوست دارید را تعیین کنید. فرض کنید سرور ما روی پورت 3010 کار می‌کند، در این حالت URL برای دسترسی به این پوشه به صورت زیر خواهد بود:

1//development URL
2http://localhost:3010/exported-images
3//production URL
4https://<your_domain>:3010/exported-images

به عنوان ملاحظه آخر باید بگوییم تنها در مواردی تصاویر باید دریک پوشه عمومی قرار گیرند که قرار باشد به یک سرویس خارجی ارسال شوند و بلافاصله پس از اجرای این کار باید حذف شوند. نکته دیگر این است که باید از رشته‌های تصادفی برای نام‌های فایل استفاده شود تا میزان امنیت بالاتر برود.

اسکریپت Jimp برای درج واترمارک و متن روی تصویر

با در نظر گرفتن ملاحظاتی که در بخش قبل بیان شدند، در این بخش یک قالب جدید خام را انتخاب می‌کنیم و یک لوگو و متن به آن الصاق می‌کنیم. تصویر نهایی چیزی مانند زیر خواهد بود. به متن و تصویر لوگوی انتهای تصویر توجه کنید:

اسکریپت

در ادامه نخستین پیاده‌سازی اسکریپت را ملاحظه می‌کنید که در ادامه آن را توضیح خواهیم داد:

1var Jimp = require('jimp');
2
3//if you are following along, create the following 2 images relative to this script:
4let imgRaw = 'raw/image1.png'; //a 1024px x 1024px backgroound image
5let imgLogo = 'raw/logo.png'; //a 155px x 72px logo
6//---
7
8let imgActive = 'active/image.jpg';
9let imgExported = 'export/image1.jpg';
10
11let textData = {
12  text: '© JKRB Investments Limited', //the text to be rendered on the image
13  maxWidth: 1004, //image width - 10px margin left - 10px margin right
14  maxHeight: 72+20, //logo height + margin
15  placementX: 10, // 10px in on the x axis
16  placementY: 1024-(72+20)-10 //bottom of the image: height - maxHeight - margin 
17};
18
19//read template & clone raw image 
20Jimp.read(imgRaw)
21  .then(tpl => (tpl.clone().write(imgActive)))
22
23  //read cloned (active) image
24  .then(() => (Jimp.read(imgActive)))
25
26  //combine logo into image
27  .then(tpl => (
28    Jimp.read(imgLogo).then(logoTpl => {
29      logoTpl.opacity(0.2);
30      return tpl.composite(logoTpl, 512-75, 512, [Jimp.BLEND_DESTINATION_OVER, 0.2, 0.2]);
31    });
32  )
33
34  //load font	
35  .then(tpl => (
36    Jimp.loadFont(Jimp.FONT_SANS_32_WHITE).then(font => ([tpl, font]))
37  ))
38	
39  //add footer text
40  .then(data => {
41
42    tpl = data[0];
43    font = data[1];
44  
45    return tpl.print(font, textData.placementX, textData.placementY, {
46      text: textData.text,
47      alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
48      alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
49    }, textData.maxWidth, textData.maxHeight);
50  })
51
52  //export image
53  .then(tpl => (tpl.quality(100).write(imgExported)))
54
55  //log exported filename
56  .then(tpl => { 
57    console.log('exported file: ' + imgExported);
58  })
59
60  //catch errors
61  .catch(err => {
62    console.error(err);
63  });

این اسکریپت یک ماهیت پیش‌رونده دارد و گردش کاری ()then نیز آن را ساده‌تر ساخته است و باعث شده که خوانایی آن افزایش یابد و بتوان با سهولت آن را پیگیری کرد. به همین دلیل احتمالاً این اسکریپت نیاز به توضیح چندانی ندارد؛ اما در ادامه برخی از بخش‌ها را که مورد علاقه توسعه‌دهنده‌های جاوا اسکریپت هستند، بررسی می‌کنیم.

تعریف کردن متغیرها

همه متغیرهای این اسکریپت در بخش ابتدایی آن تعریف شده‌اند و از این رو خوانایی افزایش یافته و به‌روزرسانی نیز آسان‌تر صورت می‌گیرد. همه متغیرهایی که با استفاده از let تعریف شده‌اند، کارکردهای مشخصی دارند.

کلون کردن تصاویر خام و باز کردن تصاویر فعال

1Jimp.read(imgRaw)
2  .then(tpl => (tpl.clone().write(imgActive))) 
3  .then(() => (Jimp.read(imgActive)))

ما از متد ()Jimp.read برای باز کردن یک تصویر خام و آغاز دستکاری آن استفاده می‌کنیم. ()Jimp.read یک promise محسوب می‌شود که شیء تصویر را که قرار است با آن کار کنیم با نام tpl بازگشت می‌دهد.

ترکیب تصویر و واترمارک

1.then(tpl => (
2   Jimp.read(imgLogo).then(logoTpl => {
3      logoTpl.opacity(0.2);
4      return tpl.composite(
5         logoTpl, 
6          512-75, 
7          512, 
8          [Jimp.BLEND_DESTINATION_OVER]);
9   });  
10)

در این بلوک ()then ما یکبار دیگر متد ()Jimp.read را فراخوانی می‌کنیم تا لوگوی واترمارک خود را بارگذاری کنیم. شفافیت لوگو در وهله نخست با استفاده از ()logoTpl.opacity تغییر می‌یابد که به یک promise نیاز ندارد.

به همین دلیل در ادامه با استفاده از ()tpl.composite تصویر parameters را روی تصویر اصلی tpl خود قرار می‌دهیم. پارامترهای مورد استفاده برای این کار کاملاً سرراست هستند. ابتدا خود لوگو را ارسال می‌کنیم، سپس مختصات x و y فرستاده می‌شوند و در نهایت حالت ترکیب (blend) تصاویر ارسال می‌شود. در این دستور به یک لوگو نیاز داریم که روی تصویر اصلی قرار گیرد.

Jimp.BLEND_DESTINATION_OVER

نکته: برای کسب اطلاعات بیشتر در مورد ترکیب تصاویر و نوع متدهایی که این بسته ارائه می‌کند، می‌توانید بخش NPMJS را در مستندات متدهای ابتدایی JIMP (+) ملاحظه کنید.

بارگذاری فونت متن

1.then(tpl => (
2   Jimp.loadFont(Jimp.FONT_SANS_32_WHITE
3   .then(font => ([tpl, font]))  
4))

در این بخش از اسکریپت دو کار انجام می‌دهیم. ابتدا فونت Sans داخلی سفید رنگ و اندازه 32 Jimp را بارگذاری می‌کنیم که به ما امکان می‌دهد تا آن را در هر فراخوانی ()tpl.print که در ادامه برای درج متن روی تصاویر انجام می‌دهیم، استفاده کنیم.

کار دوم ما این است که promise را بسط می‌دهیم و یک آرایه برای استفاده در بلوک بعدی ()then استفاده می‌کنیم. همان طور که می‌بینید بلوک ()then بعدی به شیء تصویر اصلی ما با نام tpl نیاز دارد و ما باید شیء font را بارگذاری کرده باشیم. از آنجا که جاوا اسکریپت از چندتایی‌هایی به این شکل پشتیبانی نمی‌کند، می‌توانیم یک آرایه از شیءهای مورد نیاز بازگشت دهیم.

نکته: برای این که همه چیز را در مورد بارگذاری فونت‌های نقشه‌بیتی و چاپ آن‌ها روی تصاویر بدانید می‌توانید از مستندات نگارش متن Jimp (+) استفاده کنید. ابزارهای تبدیل متن که در انتهای این مقاله معرف ‌شده‌اند برای تبدیل فونت‌ها به حالت نقشه‌بیتی یعنی فایل‌های fnt. مورد نیاز هستند. به طور خاص، Hiero (+) مفید است. به خاطر داشته باشید که تنها یک ترکیب از «اندازه-رنگ» از فونت را می‌توانید اکسپورت کنید، از این رو احتمالاً لازم خواهد بود که چندین فونت را در اسکریپت‌های پردازش تصویر خود ایمپورت کنید.

فایل‌های تصاویر معمولاً در قالب PNG هستند که بدین ترتیب می‌توانید شفافیت فونت را بیشتر تغییر داده یا ماسک کنید.

نمایش متن روی قالب‌ها

1.then(data => {
2   tpl = data[0];
3   font = data[1];
4   return tpl.print(
5      font,
6      textData.placementX, 
7      textData.placementY, 
8      {      
9         text: textData.text,      
10         alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,      
11         alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE    
12      }, 
13      textData.maxWidth, 
14      textData.maxHeight);  
15})

ما با استفاده از آرایه data شیءهای font و tpl را بازیابی می‌کنیم و ()tpl.print را برای افزودن متن به تصاویر خود فراخوانی می‌کنیم.

در این مرحله متن به بخش میانی و پایین تصویر اضافه می‌شود. print شیء فونت، مختصات x و y را می‌گیرد و در ادامه با دریافت شیئی که خود متن و راستای آن را تعریف می‌کند، اقدام به درج متن روی تصویر می‌کند. مقادیر maxWidth و maxHeight به تعیین روش عملکرد این سوگیری‌ها در زمینه تصاویر متفاوت می‌پردازند.

اکسپورت کردن و پس پردازش

1.then(tpl => (tpl.quality(100).write(imgExported)))   
2.then(tpl => {
3   console.log('exported file: ' + imgExported);  
4})
5.catch(err => {
6  console.error(err);
7 });

در 2 بلوک ()then اخیر از ()tpl.quality().write برای اکسپورت کردن تصاویر در دایرکتوری export استفاده کردیم و در log نیز پایان پردازش را اعلام نمودیم. هرگونه عملیات پس‌پردازش از جمله موارد زیر را نیز می‌توان اجرا کرد:

  • طراحی یک promise که این پردازش می‌تواند در آن میزبانی شود.
  • بازگشت دادن یک URL کامل از تصویر برای یک پاسخ API.
  • گزارش‌دهی/ذخیره‌سازی تصویر در یک پایگاه داده.
  • حذف فایل فعال یا فایل‌های بی‌استفاده باقی‌مانده
  • رفتن به یک وظیفه پردازش تصویر دیگر، در صورت وجود عملیات‌های صف‌بندی شده دیگر.

از آنجا که شما احتمالاً یک توسعه‌دهنده ارشد جاوا اسکریپت هستید به بررسی بخش ()catch نپرداختیم چون احتمالاً به کار شما نخواهد آمد. اما سعی کنید دست‌کم یک ()catch داشته باشید تا بتوانید خطاهایی که ممکن است رخ بدهند را مدیریت کنید.

سخن پایانی

در این راهنما صرفاً یکی از موارد استفاده بسته Jimp را معرفی کردیم. اگر می‌خواهید با این بسته پردازش تصویر بیشتر آشنا شوید، می‌توانید به صفحه مستندات آن روی NPMJS (+) مراجعه کنید. لینک این پروژه روی گیت‌هاب نیز در این آدرس (+) در دسترس شماست.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
rossbulat
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *