قوة الويب بالنسبة إلى الرسامين: كيفية استخدام pixiv لتقنيات الويب في تطبيق الرسم الخاص بهم

‫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، تمكّنوا من إضافة المزيد من الأنواع في تفاصيل الفرشاة وزيادة عدد الفرش المتاحة إلى سبعة.

الفرش السبع المختلفة في pixiv التي تتراوح بين الدقة والخشونة، والحدة وعدم الحدة، والدقة المنخفضة والدقة العالية، وما إلى ذلك

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

لمسة فرشاة بنسيج بسيط

تم رسم هذه الخطوط من خلال إنشاء مسارات ورسم خطوط، ولكن 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);
}

يتيح استخدام نقاط الشركاء تغيير السماكة والتظليل استجابةً لضغط الرسم، ما يسمح بالتعبير عن الخطوط القوية والضعيفة التالية، مثل:

لمسات فرشاة حادة ومتسقة ذات نهايات رفيعة

لمسة فرشاة غير حادة مع تطبيق ضغط أكبر في المنتصف

بالإضافة إلى ذلك، يمكن الآن للعمليات التي تستخدم عناصر نقطية متحركة إرفاق مواد باستخدامshader منفصل، ما يتيح تمثيلًا فعّالاً للفرشاة باستخدام مواد مثل القلم الرصاص والقلم الفلمّي.

توافق مع قلم الشاشة على المتصفّح

أصبح استخدام قلم الشاشة الرقمي أمرًا شائعًا للغاية لدى الفنانين الرقميين. تتيح browsers الحديثة استخدام 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 وكتابة برنامج التظليل، يسمح للمطوّرين باستخدام أوضاع التركيب التي لم يتم تحديدها مسبقًا من خلال واجهة برمجة التطبيقات. في المستقبل، سيتم استخدام WebGL في تطبيق pixiv Sketch لتطبيق ميزة الطبقات من أجل زيادة قابلية التوسّع والمرونة.

في ما يلي رمز نموذجي لتركيبة الطبقة:

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 API التي تجمع بين المقطع الصوتي للميكروفون الذي يتم الحصول عليه من 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، يمكنك إنشاء تطبيق معقد على النظام الأساسي للويب وتوسيع نطاقه على أي جهاز. يمكنك الاطّلاع على مزيد من المعلومات عن التقنيات التي تمّ تقديمها في دراسة الحالة هذه من خلال الاطّلاع على الرابطين التاليين: