pixiv هي خدمة منتدى على الإنترنت تتيح للرسامين ومحبي الرسوم التواصل مع بعضهم البعض من خلال المحتوى الذي ينشرونه. ويتيح للمستخدمين نشر رسوماتهم التوضيحية. يستخدم هذا التطبيق أكثر من 84 مليون مستخدم في جميع أنحاء العالم، وتم نشر أكثر من 120 مليون عمل فني عليه حتى أيار (مايو) 2023.
pixiv Sketch هي إحدى الخدمات التي تقدّمها pixiv. ويُستخدم لرسم الأعمال الفنية على الموقع الإلكتروني باستخدام الأصابع أو أقلام الرسم. يتيح هذا التطبيق مجموعة متنوعة من الميزات لرسم رسومات توضيحية رائعة، بما في ذلك أنواع عديدة من الفُرش والطبقات والرسم باستخدام أداة التعبئة، كما يتيح للمستخدمين بث عملية الرسم مباشرةً.
في دراسة الحالة هذه، سنلقي نظرة على كيفية تحسين تطبيق pixiv Sketch على الويب من حيث الأداء والجودة باستخدام بعض الميزات الجديدة لمنصة الويب، مثل WebGL وWebAssembly وWebRTC.
لماذا يجب تطوير تطبيق رسم على الويب؟
تم إطلاق pixiv Sketch لأول مرة على الويب وعلى أجهزة iOS في عام 2015. كانت الفئة المستهدفة من إصدار الويب هي أجهزة الكمبيوتر المكتبي بشكل أساسي، ولا تزال هذه الأجهزة المنصّة الأكثر استخدامًا من قِبل مجتمع الرسامين.
في ما يلي أهم سببَين دفعا شركة pixiv إلى اختيار تطوير نسخة ويب بدلاً من تطبيق على أجهزة الكمبيوتر:
- إنشاء تطبيقات لأنظمة التشغيل Windows وMac وLinux وغيرها مكلف للغاية. يمكن الوصول إلى الويب من أي متصفّح على الكمبيوتر المكتبي.
- يوفّر الويب أفضل مدى وصول على مستوى جميع المنصات. يتوفّر الويب على أجهزة الكمبيوتر المكتبي والأجهزة الجوّالة، وعلى جميع أنظمة التشغيل.
تكنولوجيا
يتضمّن تطبيق pixiv Sketch عددًا من الفُرش المختلفة التي يمكن للمستخدمين الاختيار من بينها. قبل استخدام WebGL، كان هناك نوع واحد فقط من الفُرش لأنّ لوحة العرض الثنائية الأبعاد كانت محدودة جدًا بحيث لا يمكنها عرض الملمس المعقّد للفُرش المختلفة، مثل الحواف الخشنة لقلم الرصاص وعرض وشدة الألوان المختلفة التي تتغير حسب ضغط الرسم.
أنواع مبتكرة من فُرش الرسم باستخدام WebGL
ومع ذلك، بعد اعتماد WebGL، تمكّنوا من إضافة المزيد من التنوّع في تفاصيل الفرشاة وزيادة عدد الفرش المتاحة إلى سبع.

باستخدام سياق لوحة الرسم الثنائية الأبعاد، كان من الممكن فقط رسم خطوط ذات ملمس بسيط وعرض موزّع بالتساوي، كما هو موضّح في لقطة الشاشة التالية:

تم رسم هذه الخطوط من خلال إنشاء مسارات ورسم ضربات، ولكن تعيد WebGL إنتاجها باستخدام نقاط спрайтов ومظلّلات، كما هو موضّح في نماذج الرموز التالية
يوضّح المثال التالي برنامج تظليل الرؤوس.
precision highp float;
attribute vec2 pos;
attribute float thicknessFactor;
attribute float opacityFactor;
uniform float pointSize;
varying float varyingOpacityFactor;
varying float hardness;
// Calculate hardness from actual point size
float calcHardness(float s) {
float h0 = .1 * (s - 1.);
float h1 = .01 * (s - 10.) + .6;
float h2 = .005 * (s - 30.) + .8;
float h3 = .001 * (s - 50.) + .9;
float h4 = .0002 * (s - 100.) + .95;
return min(h0, min(h1, min(h2, min(h3, h4))));
}
void main() {
float actualPointSize = pointSize * thicknessFactor;
varyingOpacityFactor = opacityFactor;
hardness = calcHardness(actualPointSize);
gl_Position = vec4(pos, 0., 1.);
gl_PointSize = actualPointSize;
}
يوضّح المثال التالي رمزًا نموذجيًا لبرنامج تظليل جزء من الشاشة.
precision highp float;
const float strength = .8;
const float exponent = 5.;
uniform vec4 color;
varying float hardness;
varying float varyingOpacityFactor;
float fallOff(const float r) {
// w is for width
float w = 1. - hardness;
if (w < 0.01) {
return 1.;
} else {
return min(1., pow(1. - (r - hardness) / w, exponent));
}
}
void main() {
vec2 texCoord = (gl_PointCoord - .5) * 2.;
float r = length(texCoord);
if (r > 1.) {
discard;
}
float brushAlpha = fallOff(r) * varyingOpacityFactor * strength * color.a;
gl_FragColor = vec4(color.rgb, brushAlpha);
}
يؤدي استخدام النقاط إلى تسهيل عملية تغيير السمك والتظليل استجابةً لضغط الرسم، ما يتيح التعبير عن الخطوط القوية والضعيفة التالية، مثل هذه:


بالإضافة إلى ذلك، يمكن الآن ربط مواد العرض باستخدام نقاط спрайты من خلال استخدام برنامج تظليل منفصل، ما يتيح عرضًا فعالاً للفرش ذات مواد العرض، مثل قلم الرصاص وقلم اللباد.
التوافق مع قلم الشاشة على المتصفّح
أصبح استخدام القلم الرقمي شائعًا جدًا بين الفنانين الرقميين. تتيح المتصفحات الحديثة PointerEvent API التي تتيح للمستخدمين استخدام قلم شاشة على أجهزتهم: استخدِم PointerEvent.pressure لقياس ضغط القلم، واستخدِم PointerEvent.tiltX وPointerEvent.tiltY لقياس زاوية القلم بالنسبة إلى الجهاز.
لتنفيذ ضربات الفرشاة باستخدام صورة نقطية، يجب أن يتم استيفاء PointerEvent وتحويلها إلى تسلسل أحداث أكثر دقة. في PointerEvent، يمكن الحصول على اتجاه القلم الرقمي على شكل إحداثيات قطبية، لكن يحوّلها تطبيق pixiv Sketch إلى متّجه يمثّل اتجاه القلم الرقمي قبل استخدامها.
function getTiltAsVector(event: PointerEvent): [number, number, number] {
const u = Math.tan((event.tiltX / 180) * Math.PI);
const v = Math.tan((event.tiltY / 180) * Math.PI);
const z = Math.sqrt(1 / (u * u + v * v + 1));
const x = z * u;
const y = z * v;
return [x, y, z];
}
function handlePointerDown(event: PointerEvent) {
const position = [event.clientX, event.clientY];
const pressure = event.pressure;
const tilt = getTiltAsVector(event);
interpolateAndRender(position, pressure, tilt);
}
طبقات رسم متعددة
الطبقات هي إحدى أكثر المفاهيم تميزًا في الرسم الرقمي. تتيح هذه التطبيقات للمستخدمين رسم أجزاء مختلفة من الرسومات التوضيحية فوق بعضها البعض، كما تتيح إجراء تعديلات على كل طبقة على حدة، وتوفّر pixiv Sketch وظائف الطبقات مثل تطبيقات الرسم الرقمي الأخرى.
يمكن عادةً تنفيذ الطبقات باستخدام عدة عناصر <canvas> مع drawImage() وعمليات التركيب. ومع ذلك، يشكّل ذلك مشكلة لأنّه في سياق لوحة الرسم الثنائية الأبعاد، ليس هناك خيار آخر سوى استخدام وضع التركيب CanvasRenderingContext2D.globalCompositeOperation، وهو وضع محدّد مسبقًا ويحدّ بشكل كبير من قابلية التوسّع. من خلال استخدام WebGL وكتابة برنامج التظليل، يمكن للمطوّرين استخدام أوضاع التركيب غير المحدّدة مسبقًا من خلال واجهة برمجة التطبيقات. في المستقبل، ستنفّذ منصة pixiv Sketch ميزة الطبقات باستخدام WebGL لتحقيق قابلية أكبر للتوسّع والمرونة.
في ما يلي نموذج الرمز البرمجي لتركيب الطبقات:
precision highp float;
uniform sampler2D baseTexture;
uniform sampler2D blendTexture;
uniform mediump float opacity;
varying highp vec2 uv;
// for normal mode
vec3 blend(const vec4 baseColor, const vec4 blendColor) {
return blendColor.rgb;
}
// for multiply mode
vec3 blend(const vec4 baseColor, const vec4 blendColor) {
return blendColor.rgb * blendColor.rgb;
}
void main()
{
vec4 blendColor = texture2D(blendTexture, uv);
vec4 baseColor = texture2D(baseTexture, uv);
blendColor.a *= opacity;
float a1 = baseColor.a * blendColor.a;
float a2 = baseColor.a * (1. - blendColor.a);
float a3 = (1. - baseColor.a) * blendColor.a;
float resultAlpha = a1 + a2 + a3;
const float epsilon = 0.001;
if (resultAlpha > epsilon) {
vec3 noAlphaResult = blend(baseColor, blendColor);
vec3 resultColor =
noAlphaResult * a1 + baseColor.rgb * a2 + blendColor.rgb * a3;
gl_FragColor = vec4(resultColor / resultAlpha, resultAlpha);
} else {
gl_FragColor = vec4(0);
}
}
تلوين مساحة كبيرة باستخدام وظيفة التعبئة
كانت تطبيقات pixiv Sketch على iOS وAndroid توفّر ميزة "الحزمة"، ولكن لم تكن متاحة في إصدار الويب. تم تنفيذ إصدار التطبيق من دالة التقسيم إلى مجموعات في C++.
بما أنّ قاعدة الرموز البرمجية متوفرة بلغة C++، استخدم تطبيق pixiv Sketch كلاً من Emscripten وasm.js لتنفيذ وظيفة التعبئة في إصدار الويب.
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
وقد أتاح استخدام asm.js تقديم حلّ فعّال. عند مقارنة وقت تنفيذ JavaScript العادي بوقت تنفيذ asm.js، نجد أنّ وقت التنفيذ باستخدام asm.js ينخفض بنسبة %67. ومن المتوقّع أن يكون الأداء أفضل عند استخدام WASM.
تفاصيل الاختبار:
- الطريقة: طلاء مساحة 1180x800 بكسل باستخدام وظيفة التعبئة
- الجهاز الاختباري: MacBook Pro (M1 Max)
وقت التنفيذ:
- JavaScript فقط: 213.8 ملي ثانية
- asm.js: 70.3 ملي ثانية
باستخدام Emscripten وasm.js، تمكّن تطبيق pixiv Sketch من طرح ميزة "التقسيم إلى مجموعات" بنجاح من خلال إعادة استخدام قاعدة الرموز البرمجية من إصدار التطبيق الخاص بالمنصة.
البث المباشر أثناء الرسم
توفّر منصة pixiv Sketch ميزة البث المباشر أثناء الرسم من خلال تطبيق الويب pixiv Sketch LIVE. وتستخدم هذه الميزة واجهة برمجة تطبيقات WebRTC، حيث تجمع بين مقطع الصوت من الميكروفون الذي تم الحصول عليه من getUserMedia() ومقطع الفيديو MediaStream الذي تم استرجاعه من العنصر <canvas>.
const canvasElement = document.querySelector('#DrawCanvas');
const framerate = 24;
const canvasStream = canvasElement.captureStream(framerate);
const videoStreamTrack = canvasStream.getVideoTracks()[0];
const audioStream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: {},
});
const audioStreamTrack = audioStream.getAudioTracks()[0];
const stream = new MediaStream();
stream.addTrack(audioStreamTrack.clone());
stream.addTrack(videoStreamTrack.clone());
الاستنتاجات
وبفضل قوة واجهات برمجة التطبيقات الجديدة، مثل WebGL وWebAssembly وWebRTC، يمكنك إنشاء تطبيق معقّد على منصة الويب وتوسيع نطاقه ليشمل أي جهاز. يمكنك الاطّلاع على مزيد من المعلومات حول التقنيات الموضّحة في دراسة الحالة هذه من خلال الروابط التالية:
- WebGL
- يمكنك أيضًا الاطّلاع على WebGPU، وهو الإصدار الأحدث من WebGL.
- WebAssembly
- WebRTC
- المقالة الأصلية باللغة اليابانية