قوة الويب بالنسبة إلى الرسامين: كيفية استخدام 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

ومع ذلك، تمكّنوا من إضافة المزيد من التنوع في تفاصيل الفرشاة وزيادة عدد الفُرش المتاحة إلى سبع فُرش.

الفُرش السبع المختلفة في 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);
}

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

فرشاة حادة ومتساوية بنهايات رفيعة.

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

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

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

وأصبح استخدام قلم الشاشة الرقمي رائجًا للغاية لدى الفنّانين الرقميين. تتوافق المتصفّحات الحديثة مع 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.

تفاصيل الاختبار:

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