قدرت وب برای تصویرگران: چگونه pixiv از فناوری های وب برای برنامه طراحی خود استفاده می کند

pixiv یک سرویس اجتماعی آنلاین برای تصویرگران و علاقه‌مندان به تصویرسازی است تا از طریق محتوای خود با یکدیگر ارتباط برقرار کنند. این سرویس به افراد اجازه می‌دهد تصاویر خود را ارسال کنند. آنها بیش از ۸۴ میلیون کاربر در سراسر جهان دارند و بیش از ۱۲۰ میلیون اثر هنری تا ماه مه ۲۰۲۳ ارسال کرده‌اند.

pixiv Sketch یکی از سرویس‌های ارائه شده توسط pixiv است. از این سرویس برای طراحی آثار هنری در وب‌سایت، با استفاده از انگشت یا قلم استفاده می‌شود. این سرویس از ویژگی‌های متنوعی برای طراحی تصاویر شگفت‌انگیز، از جمله انواع مختلف قلم‌مو، لایه‌ها و نقاشی سطلی پشتیبانی می‌کند و همچنین به افراد امکان می‌دهد فرآیند طراحی خود را به صورت زنده پخش کنند.

در این مطالعه موردی، نگاهی خواهیم انداخت به اینکه چگونه pixiv Sketch با استفاده از برخی ویژگی‌های جدید پلتفرم وب مانند WebGL، WebAssembly و WebRTC، عملکرد و کیفیت برنامه وب خود را بهبود بخشیده است.

چرا باید یک اپلیکیشن طراحی تحت وب توسعه دهیم؟

pixiv Sketch اولین بار در سال ۲۰۱۵ در وب و iOS منتشر شد. مخاطب هدف آنها برای نسخه وب، در درجه اول دسکتاپ بود که هنوز هم اصلی‌ترین پلتفرم مورد استفاده جامعه تصویرسازی است.

در اینجا دو دلیل اصلی pixiv برای انتخاب توسعه نسخه وب به جای برنامه دسکتاپ آورده شده است:

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

فناوری

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);
}

استفاده از نقاط نقطه‌ای، تغییر ضخامت و سایه‌زنی را در پاسخ به فشار ترسیم، ساده می‌کند و امکان بیان خطوط قوی و ضعیف زیر را فراهم می‌کند، مانند این‌ها:

ضربه قلم موی تیز و یکنواخت با انتهای نازک.

حرکت قلم‌مو را با فشار بیشتر در وسط، غیرشفاف کنید.

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

پشتیبانی از قلم استایلوس در مرورگر

استفاده از قلم دیجیتال برای هنرمندان دیجیتال بسیار محبوب شده است. مرورگرهای مدرن از رابط برنامه‌نویسی کاربردی PointerEvent پشتیبانی می‌کنند که به کاربران امکان می‌دهد از قلم روی دستگاه خود استفاده کنند: از 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 و نوشتن shader، به توسعه‌دهندگان این امکان را می‌دهد که از حالت‌های ترکیب که توسط API از پیش تعریف نشده‌اند، استفاده کنند. در آینده، 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);
  }
}

نقاشی روی سطوح بزرگ با عملکرد سطل

اپلیکیشن‌های iOS و اندروید pixiv Sketch از قبل قابلیت bucket را ارائه می‌دادند، اما نسخه وب این قابلیت را نداشت. نسخه اپلیکیشنی این قابلیت bucket با زبان ++C پیاده‌سازی شده بود.

با توجه به اینکه کدبیس از قبل به زبان ++C موجود بود، pixiv Sketch از Emscripten و asm.js برای پیاده‌سازی تابع bucket در نسخه وب استفاده کرد.

bfsQueue.push(startPoint);

while (!bfsQueue.empty()) {
  Point point = bfsQueue.front();
  bfsQueue.pop();
  /* ... */
  bfsQueue.push(anotherPoint);
}

استفاده از asm.js یک راهکار کارآمد را ممکن ساخت. با مقایسه زمان اجرای جاوا اسکریپت خالص در مقابل asm.js ، زمان اجرا با استفاده از asm.js 67٪ کاهش می‌یابد. انتظار می‌رود این زمان با استفاده از WASM حتی بهتر هم باشد.

جزئیات آزمایش:

  • چگونه: با استفاده از تابع سطل رنگ، ناحیه‌ای با ابعاد ۱۱۸۰x۸۰۰ پیکسل را رنگ‌آمیزی کنید
  • دستگاه تست: مک‌بوک پرو (M1 Max)

زمان اجرا:

  • جاوا اسکریپت خالص: ۲۱۳.۸ میلی‌ثانیه
  • asm.js: ۷۰.۳ میلی‌ثانیه

با استفاده از Emscripten و asm.js ، pixiv Sketch توانست با استفاده مجدد از کدبیس نسخه اپلیکیشن مخصوص پلتفرم، ویژگی bucket را با موفقیت منتشر کند.

پخش زنده هنگام طراحی

pixiv Sketch از طریق برنامه وب pixiv Sketch LIVE، قابلیت پخش زنده در حین طراحی را ارائه می‌دهد. این قابلیت از API 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());

نتیجه‌گیری

با قدرت APIهای جدید مانند WebGL، WebAssembly و WebRTC، می‌توانید یک برنامه پیچیده را روی پلتفرم وب ایجاد کنید و آن را در هر دستگاهی مقیاس‌پذیر کنید. می‌توانید در لینک‌های زیر درباره فناوری‌های معرفی‌شده در این مطالعه موردی اطلاعات بیشتری کسب کنید: