إذا سبق لك العمل على مشاريع تتطلب تعديل عدد كبير من الصور مع تغيير البيانات وأماكنها في كل مرة، فحتما أنك تعرف أن أكثر ما يستهلك الوقت ليس التصميم نفسه، بل تعديل المعلومات يدويا، وهو وقت كان يجب استغلاله في الإبداع.
واجهت هذه المشكلة في تصاميم رياضية أحدث فيها بيانات الجولة والمباريات بعد كل جولة، ترتيب الفرق وعدد النقاط والأهداف في تصاميم كرة القدم، وعدد الميداليات في مشاريع أخرى مثل مسابقة ألعاب البحر الأبيض المتوسط. تكرار هذه العملية يدويا استغرق وقتا طويلا وكان مملا، والأسوأ أن احتمال الأخطاء فيه عالٍ.
بدل إعادة تعديل كل صورة يدويا، سأقدم لك طريقة يستعملها المحترفون تجعل Photoshop ينفذ العملية تلقائيا. و هي الـ Scripting .
في هذا المقال سأشارك الفكرة التي اعتمدت عليها، وكيف ساعدني هذا الأسلوب على توفير الوقت والجهد في المشاريع التي تتطلب تحديث بيانات مستمر.
تحضير الملفات
قبل كتابة أي سطر من السكريبت أو أي تصميم في Photoshop، أول شيء أفعله هو تجهيز جميع الملفات التي سأحتاجها. خطوة بسيطة، لكنها توفر وقتا كبيرا خاصة في المشاريع ذات العدد الكبير من الصور والبيانات.
في هذا المثال، أنشأت مجلدا خاصا بالمشروع على سطح المكتب، ووضعت بداخله جميع صور الأعلام التي سأستخدمها، بالإضافة إلى ملف JSX الذي سأكتب فيه السكريبت.
أفضل دائما جمع كل شيء داخل مجلد واحد، لأن السكريبت يحتاج الوصول إلى هذه الملفات لاحقا. إذا كانت الصور موزعة في أماكن مختلفة، تصبح إدارة المشروع صعبة، خاصة عند التعديل أو النقل إلى جهاز آخر. أنصح بشدة بهذه الخطوة.
بعد تجهيز الصور، أنتقل إلى إنشاء ملف JSX، الملف الذي سأكتب بداخله السكريبت. إذا لم يسبق لك التعامل مع هذا النوع من الملفات، فهو في النهاية ملف نصي يحتوي أوامر ينفذها Photoshop.
هذا المقال لا يشرح أساسيات البرمجة أو سكربتات Photoshop، ويُفضل امتلاك معرفة مسبقة بمبادئ البرمجة أو على الأقل بآلية كتابة السكربتات داخل البرنامج. الهدف هنا ليس تعليم البرمجة، بل توضيح الفكرة التي تمكّن Photoshop من تعديل البيانات والصور تلقائيا بدل تنفيذها يدويا.
قبل البدء، تأكد من أمرين:
- أن جميع الصور التي ستستخدمها موجودة داخل مجلد واحد.
- أن أسماء الملفات واضحة ومنظمة، لأن السكريبت يعتمد عليها أثناء استبدال الصور داخل التصميم.
قد تبدو هذه الخطوة مجرد ترتيب ملفات، لكنها في الحقيقة الأساس الذي يعتمد عليه كل ما سنقوم به لاحقا.
في مشاريعي الحقيقية، قد يصل عدد الصور إلى عشرات أو حتى مئات الملفات، لذلك أبدأ دائما بتنظيمها أولا. كل دقيقة أقضيها في ترتيب الملفات في البداية، توفر وقتا أكبر أثناء العمل.
تحضير التصميم في Photoshop
بعد تجهيز الملفات، أفتح Photoshop لتصميم القالب الذي سيعدله السكريبت لاحقا. المثال هنا بوستر سوشل ميديا لأكثر المنتخبات تسجيلا للأهداف، ومثال آخر لموعد المباريات.
لن أشرح طريقة تصميم القالب نفسه، لأن ذلك ليس هدف المقال، لكن هناك أمور أحرص عليها دائما قبل كتابة السكريبت، لأنها تؤثر مباشرة على نجاح العملية.
أبدأ بتصميم البوستر بشكل طبيعي، وأضع جميع العناصر التي أريد ظهورها في النتيجة النهائية. في هذا المثال اخترت منتخب الولايات المتحدة كبيانات تجريبية، فوضعت العلم واسم المنتخب وعدد الأهداف كما لو كنت أصمم الصورة النهائية.
لماذا هذا المنتخب تحديدا؟
يمكنك اختيار أي اسم أو نص تريده. اخترت هذا المنتخب لأن اسمه يتكون من كلمتين، ما يتيح اختبار قدرة التصميم على استيعاب أسماء طويلة نسبيا دون التأثير على التنسيق.
أثناء التصميم لا تهمني كثيرا البيانات التي أضعها، بقدر ما يهمني تجربة أسوأ الاحتمالات قبل بدء السكريبتينغ.
بعد توزيع العناصر، أبدأ بتنظيم الطبقات داخل Photoshop. هذه الخطوة ليست مجرد ترتيب، بل جزء أساسي من نجاح السكريبت.
أجمّع العناصر الثابتة داخل مجلدات مستقلة، مثل الخلفية والعنوان أو أي عنصر لن يتغير بين صورة وأخرى، وأقفل (Lock) هذه الطبقات حتى لا أعدلها بالخطأ أثناء العمل.
أما العناصر التي سيتحكم بها السكريبت، فأضع كل نوع داخل مجلد مستقل. في هذا المثال أنشأت مجلدا لأسماء المنتخبات، وآخر للأعلام، وآخر لعدد الأهداف.
بهذه الطريقة يسهل على السكريبت الوصول إلى العناصر التي يحتاج تعديلها، دون التأثير على بقية التصميم.
نقطة أخرى من أهم النقاط في هذا المشروع: ترتيب الطبقات داخل كل مجلد.
يجب أن يطابق ترتيبها في لوحة Layers ترتيبها على الـ Canvas. أول عنصر يظهر في التصميم يجب أن يكون أول طبقة داخل المجلد، والثاني يقابله الثاني، وهكذا حتى آخر عنصر.
إذا كان أول علم في التصميم في أعلى القائمة، يجب أن يكون هو أول طبقة داخل مجلد "علم"، كما هو موضح في الصورة، وينطبق الأمر نفسه على أسماء المنتخبات وعدد الأهداف وأي عناصر أخرى يتحكم بها السكريبت. السبب: عملية الـ Loop لا تبحث عن اسم الطبقة، بل تتعامل مع الطبقات حسب ترتيبها داخل المجلد. إذا اختلف الترتيب عن ترتيب العناصر في التصميم، ستظهر البيانات في أماكن خاطئة، مثل أن يأخذ منتخب علم منتخب آخر، أو يوضع عدد الأهداف في المكان الخطأ.
لهذا السبب أقضي بضع دقائق إضافية في تنظيم الطبقات قبل البرمجة، لأن هذا يوفر وقتا كبيرا لاحقا، ويجنبني أخطاء يصعب اكتشافها بعد تنفيذ السكريبت.
تجهيز البيانات داخل ملف JSX
بعد تجهيز التصميم، بدأت العمل على ملف JSX الذي يحتوي السكريبت. أول جزء أكتبه هو البيانات، وهي المعلومات التي أريد أن يضعها السكريبت داخل التصميم.
في هذا المثال، أنشأت مصفوفة JSON تحتوي جميع المنتخبات التي أريد عرضها، ولكل منتخب ثلاث معلومات فقط:
- اسم المنتخب.
- عدد الأهداف.
- رابط صورة العلم الموجودة على الحاسوب.
// codevar data = [ {"country":" فرنسا","goals":13,"flag":"file:///C:/Users/billkerx/Desktop/imgs/fra.png" }, {"country":" هولندا","goals":11,"flag":"file:///C:/Users/billkerx/Desktop/imgs/ned.png" }, {"country":" الارجنتين","goals":11,"flag":"file:///C:/Users/billkerx/Desktop/imgs/arg.png" }]
كل منتخب عنصر مستقل داخل المصفوفة، وبهذه الطريقة يمر السكريبت على جميع المنتخبات واحدا تلو الآخر ويحدّث التصميم تلقائيا.
إدخال هذه البيانات يدويا يبدو متعبا في هذا المثال، لكنه مختلف في المشاريع الحقيقية. غالبا أحصل على هذه المعلومات من مصدر واحد، مثل ملف Excel أو قاعدة بيانات أو ملف JSON مستقل، ثم أحوّلها إلى هذا الشكل ليقرأها السكريبت بسهولة.
أما رابط صورة العلم، فطريقة الحصول عليه بسيطة. بما أن جميع الأعلام في مجلد واحد، فتحت إحدى الصور بالمتصفح ونسخت مسارها، ثم غيرت اسم الملف في نهاية الرابط حسب كل علم.
بهذه الطريقة لا أبحث عن مسار كل صورة بشكل منفصل، لأن جميع الصور في المكان نفسه، والاختلاف الوحيد بينها اسم الملف.
خطوة تبدو بسيطة، لكنها هي التي تجعل السكريبت مرنا. عند تغيير البيانات، لن أحتاج لتعديل التصميم أو إعادة كتابة الكود، يكفي تحديث هذه القائمة، وسيغير السكريبت الصور تلقائيا.
ملاحظة من تجربتي
في المشاريع الصغيرة أكتب البيانات يدويا كما في هذا المثال، لتبسيط الفكرة. أما في المشاريع التي تحتوي مئات العناصر، أفضل دائما ربط السكريبت بمصدر بيانات خارجي مثل Excel، لأن ذلك يجعل التحديث أسرع بكثير دون تعديل الكود في كل مرة.
// codevar layers = app.activeDocument.artLayers; var flag = app.activeDocument.layerSets.getByName("علم"); var country = app.activeDocument.layerSets.getByName("المنتخب"); var goals = app.activeDocument.layerSets.getByName("اهداف");
هذا الكود يعرّف ثلاثة متغيرات، كل واحد يشير إلى مجلد معين داخل التصميم.
نقطة مهمة جدا: الدالة getByName() تبحث عن المجلد اعتمادا على اسمه. يجب أن يكون الاسم داخل الكود مطابقا تماما لاسم المجلد داخل Photoshop، حتى في المسافات أو طريقة الكتابة.
إذا كان اسم المجلد "المنتخب"، لا يمكن كتابة "المنتخبات" أو "منتخب" داخل الكود، لأن السكريبت ببساطة لن يجده وسيتوقف عن العمل.
لهذا أنصح دائما، بعد تنظيم الطبقات، بالتأكد من تطابق أسماء المجلدات داخل Photoshop مع الأسماء المستخدمة في السكريبت. هذا من أكثر الأخطاء التي وقعت فيها أثناء هذا النوع من المشاريع؛ كنت أتفاجأ بعدم عمل السكريبت وأجري تعديلات كثيرة لأكتشف لاحقا أن الخطأ كان في اسم مجلد. تجنّب هذا الخطأ يوفر لك وقتا كبيرا.
آلية التكرار (Loop)
بعد جهوزية البيانات، ومعرفة السكريبت لمكان العناصر داخل التصميم، تأتي مرحلة تنفيذ جميع التعديلات تلقائيا.
استخدمت حلقة تكرار (Loop) تنفذ نفس العملية على جميع البيانات تلقائيا. بما أن جميع المنتخبات تمر بنفس خطوات التعديل، لم يكن منطقيا تكرار نفس الأوامر لكل واحد منها.
في كل دورة، يمر السكريبت على منتخب واحد فقط، وينفذ عليه جميع التعديلات قبل الانتقال إلى المنتخب التالي.
// codefor (i=0;i<data.length;i++){ var layer = country.layers[i]; layer.textItem.contents=data[i].country; var layer1 = goals.layers[i]; layer1.textItem.contents=data[i].goals; replaceContents(data[i].flag, flag.layers[i]); }
يغيّر أولا اسم المنتخب، ثم عدد الأهداف، ثم يستبدل صورة العلم بصورة المنتخب المطابقة.
بعد هذه الخطوات، يعيد تنفيذ العملية نفسها على العنصر التالي، ويستمر حتى آخر منتخب في البيانات.
لهذا كان ترتيب الطبقات الذي تحدثت عنه سابقا مهما جدا: السكريبت يعتمد على أن يقابل أول عنصر في البيانات أول عنصر في مجلدات Photoshop، والثاني الثاني، وهكذا حتى النهاية. إذا اختلف هذا الترتيب، ستظهر أسماء المنتخبات أو الأعلام في أماكن خاطئة.
الحلقة لا تحتوي أوامر كثيرة، لكنها الجزء الأهم الذي وفر معظم الوقت. بدل تعديل كل منتخب يدويا، يكرر السكريبت العملية على جميع العناصر خلال ثوان فقط.
دالة تغيير الصور
بقيت خطوة واحدة: استبدال صور الأعلام داخل التصميم.
كتبت دالة باسم replaceContents()، مسؤولة عن تنفيذ جميع العمليات المتعلقة بالصور. عند استدعائها من الحلقة، تستبدل العلم القديم بالجديد، مع الحفاظ على جميع الإعدادات التي جهزتها مسبقا في Photoshop.
// codefunction replaceContents(newFile, theSO) { var doc = app.activeDocument; var parentGroup = theSO.parent; var layerIndex = theSO.itemIndex; var oldBounds = theSO.bounds; var oldW = oldBounds[2].as('px') - oldBounds[0].as('px'); var oldH = oldBounds[3].as('px') - oldBounds[1].as('px'); var oldCenterX = oldBounds[0].as('px') + oldW / 2; var oldCenterY = oldBounds[1].as('px') + oldH / 2; var oldName = theSO.name; var oldOpacity = theSO.opacity; var oldBlend = theSO.blendMode; var oldVisible = theSO.visible; doc.activeLayer = theSO; var idPlc = charIDToTypeID("Plc "); var descPlace = new ActionDescriptor(); descPlace.putPath(charIDToTypeID("null"), new File(newFile)); executeAction(idPlc, descPlace, DialogModes.NO); var placedLayer = doc.activeLayer; placedLayer.rasterize(RasterizeType.ENTIRELAYER); var b = placedLayer.bounds; var newW = b[2].as('px') - b[0].as('px'); var newH = b[3].as('px') - b[1].as('px'); var scale = Math.min(oldW / newW, oldH / newH) * 100; placedLayer.resize(scale, scale, AnchorPosition.TOPLEFT); var b2 = placedLayer.bounds; var curW = b2[2].as('px') - b2[0].as('px'); var curH = b2[3].as('px') - b2[1].as('px'); var curCenterX = b2[0].as('px') + curW / 2; var curCenterY = b2[1].as('px') + curH / 2; placedLayer.translate(oldCenterX - curCenterX, oldCenterY - curCenterY); placedLayer.name = oldName; placedLayer.opacity = oldOpacity; placedLayer.blendMode = oldBlend; placedLayer.visible = oldVisible; if (placedLayer.parent !== parentGroup) { placedLayer.move(parentGroup, ElementPlacement.PLACEATBEGINNING); } try { theSO.remove(); } catch(e) {} return placedLayer; }
الدالة طويلة مقارنة ببقية أجزاء السكريبت، لكن لا حاجة لفهم كل سطر فيها لاستخدامها. تعمل في الخلفية، ومهمتها تنفيذ جميع العمليات تلقائيا دون تدخل يدوي.
من بين مهامها:
- استيراد الصورة الجديدة.
- ضبط حجمها ليتناسب مع التصميم.
- وضعها في مكان الصورة السابقة نفسه.
- الحفاظ على خصائص الطبقة مثل الاسم والشفافية ووضع المزج.
- حذف الصورة القديمة بعد استبدالها.
بفضل هذه الدالة، لم أعد أغير كل علم يدويا أو أعيد ضبط حجمه ومكانه في كل مرة؛ يقوم السكريبت بكل ذلك تلقائيا بمجرد تشغيله.
المثال الثاني: تصميم مواعيد ونتائج المباريات
في المثال الأول عملت على مجموعة بيانات واحدة: اسم المنتخب، العلم، وعدد الأهداف. في مشاريع أخرى نحتاج مجموعتين من البيانات أو أكثر داخل نفس التصميم، مثل مباريات كرة القدم التي تضم منتخبين، ولكل واحد اسمه وشعاره.
في هذه الحالة، أفضل دائما تقسيم عناصر كل منتخب داخل مجلد مستقل، بدل وضع جميع الأعلام أو الأسماء في مجلد واحد. بهذه الطريقة يصبح السكريبت أسهل قراءة وتعديلا، خاصة عند تعقيد المشروع.
في هذا المثال، أصبح ملف البيانات يحتوي معلومات كل منتخب، بالإضافة إلى توقيت المباراة.
لكل منتخب بياناته الخاصة، لذلك قسمت المجلدات داخل Photoshop بالطريقة نفسها.
منطق العمل لم يتغير كثيرا؛ أصبحت الحلقة تحدّث بيانات المنتخب الأول، ثم الثاني، ثم توقيت المباراة، وأخيرا تستدعي دالة replaceContents() مرتين، مرة لكل علم.
// codevar data = [ {"country1":" البرازيل","country2":" النريوج","time":"21:00","flag1":"file:///C:/Users/BillkerX/Desktop/imgs/bra.png","flag2":"file:///C:/Users/BillkerX/Desktop/imgs/nor.png", }, {"country1":" المكسيك","country2":" انجلترا","time":"01:00","flag1":"file:///C:/Users/BillkerX/Desktop/imgs/mex.png","flag2":"file:///C:/Users/BillkerX/Desktop/imgs/eng.png", }, {"country1":" البرتغال","country2":" اسبانيا","time":"20:00","flag1":"file:///C:/Users/BillkerX/Desktop/imgs/por.png","flag2":"file:///C:/Users/BillkerX/Desktop/imgs/esp.png", }, ]
الفكرة بقيت نفسها، والاختلاف الوحيد أنني أتعامل مع مجموعتين من البيانات بدل مجموعة واحدة.
أتبع هذه الطريقة حتى في المشاريع الأكبر، لأنها تجعل الكود أوضح وأسهل تعديلا لاحقا، وتقلل احتمال الأخطاء عند إضافة عناصر جديدة.
في ماذا يمكن استخدام هذه الفكرة؟
قد يبدو هذا السكريبت مخصصا للمباريات أو جداول الترتيب، لكنه في الحقيقة يصلح لأي مشروع يعتمد على قالب ثابت وبيانات تتغير باستمرار.
من الأمثلة المناسبة:
- بطاقات نتائج مباريات كرة القدم أو أي رياضة أخرى.
- جداول ترتيب البطولات.
- بطاقات الإحصائيات الخاصة باللاعبين أو المنتخبات.
- صور الأخبار العاجلة التي يتغير فيها العنوان والصورة فقط.
- بطاقات المنتجات في المتاجر الإلكترونية.
- منشورات عروض الأسعار والخصومات.
- شهادات المشاركة أو التخرج التي يتغير فيها الاسم فقط.
- بطاقات الموظفين أو أعضاء الفريق داخل الشركات.
- منشورات مواقع التواصل الاجتماعي التي تعتمد على قالب ثابت مع بيانات مختلفة.
- بطاقات الاقتباسات اليومية أو الآيات أو الأحاديث، حيث يتغير النص مع بقاء التصميم نفسه.
أسئلة شائعة
السكريبت يعطي خطأ ولا يعمل، من أين أبدأ التشخيص؟
تحقق أولا من تطابق أسماء المجلدات في Photoshop مع الأسماء المكتوبة في الكود حرفيا، بما في ذلك المسافات. هذا السبب الأكثر شيوعا لتوقف السكريبت، كما ذكرت في تجربتي أعلاه.
هل يجب أن تكون كل الصور بنفس المقاسات قبل استخدام السكريبت؟
لا، دالة replaceContents() تضبط حجم كل صورة جديدة تلقائيا لتطابق أبعاد الصورة القديمة في مكانها، لذلك لا داعي لتوحيد مقاسات الصور مسبقا.
هل تعمل هذه الطريقة على أنظمة macOS أيضا، أم Windows فقط؟
الفكرة والكود يعملان على النظامين، لكن مسار الملفات يختلف: على macOS تُكتب المسارات بصيغة مختلفة عن مسارات Windows المستخدمة في هذا المثال (مثل file:///C:/...)، فتحتاج تعديل صيغة الروابط حسب نظامك.
ماذا لو أردت إضافة عنصر رابع للتحكم فيه غير الاسم والعلم والأهداف؟
أضف مجلدا جديدا في Photoshop لهذا العنصر، عرّف متغيرا جديدا له بنفس طريقة getByName()، وأضف سطرا داخل حلقة الـ Loop يحدّث هذا العنصر بنفس منطق تحديث الاسم أو عدد الأهداف.
هل يمكن ربط البيانات مباشرة بملف Excel بدل كتابتها يدويا داخل الكود؟
نعم، وهي الطريقة التي أفضلها في المشاريع الكبيرة. يمكن قراءة بيانات من ملف CSV أو Excel وتحويلها إلى نفس صيغة مصفوفة JSON المستخدمة في هذا المقال، بدل كتابتها يدويا في كل مرة.