Çiziciler için web'in gücü: pixiv, çizim uygulamaları için web teknolojilerini nasıl kullanıyor?

pixiv, illüstratörlerin ve illüstrasyon tutkunlarının içerikleri aracılığıyla birbirleriyle iletişim kurabileceği bir online topluluk hizmetidir. Kullanıcıların kendi çizimlerini yayınlamasına olanak tanır. Dünya genelinde 84 milyondan fazla kullanıcıya sahip olan şirket, Mayıs 2023 itibarıyla 120 milyondan fazla sanat eseri yayınladı.

pixiv Sketch, pixiv tarafından sunulan hizmetlerden biridir. Parmak veya ekran kalemi kullanarak web sitesinde poster çizmek için kullanılır. Uygulamada, sayısız fırça türü, katman ve boya kovası gibi özelliklerle harika resimler çizebilir, çizim sürecinizi canlı olarak yayınlayabilirsiniz.

Bu örnek olayda, pixiv Sketch'in WebGL, WebAssembly ve WebRTC gibi yeni web platformu özelliklerini kullanarak web uygulamasının performansını ve kalitesini nasıl iyileştirdiğini inceleyeceğiz.

Neden web'de bir çizim uygulaması geliştirmelisiniz?

pixiv Sketch ilk olarak 2015'te web'de ve iOS'te kullanıma sunulmuştur. Web sürümü için hedef kitleleri, illüstrasyon topluluğu tarafından hâlâ en çok kullanılan platform olan masaüstüydü.

pixiv'in masaüstü uygulaması yerine web sürümü geliştirmeyi tercih etmesinin en önemli iki nedeni şunlardır:

  • Windows, Mac, Linux ve diğer platformlar için uygulama oluşturmak çok maliyetlidir. Web, masaüstündeki tüm tarayıcılara ulaşır.
  • Web, platformlar arasında en iyi erişime sahiptir. Web, masaüstü bilgisayarlarda, mobil cihazlarda ve tüm işletim sistemlerinde kullanılabilir.

Teknoloji

pixiv Sketch'te kullanıcıların aralarından seçim yapabileceği çeşitli fırçalar bulunur. WebGL'nin benimsenmesinden önce, 2D kanvas farklı fırçaların karmaşık dokusunu (ör. kalemlerin pürüzlü kenarları ve eskiz basıncına göre değişen farklı genişlik ve renk yoğunluğu) göstermek için çok sınırlı olduğundan yalnızca bir fırça türü vardı.

WebGL kullanan fırça reklam öğesi türleri

Ancak WebGL'i kullanmaya başladıktan sonra fırça ayrıntılarına daha fazla çeşitlilik ekleyebildiler ve mevcut fırça sayısını yedi adede çıkardılar.

İnceden kabaya, keskin olmayandan keskine, pikselliden pürüzsüzlere kadar farklı fırçalar

2D kanvas bağlamı kullanıldığında, yalnızca aşağıdaki ekran görüntüsü gibi eşit olarak dağıtılmış genişliğe sahip basit bir dokuya sahip çizgiler çizilebiliyordu:

Basit dokuya sahip fırça darbesi.

Bu çizgiler, yol oluşturarak ve çizgi çizerek çizilmiştir ancak WebGL bunu aşağıdaki kod örneklerinde gösterilen nokta sprite'leri ve gölgelendiricileri kullanarak yeniden oluşturur.

Aşağıdaki örnekte bir köşe üstü gölgelendirici gösterilmektedir.

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

Aşağıdaki örnekte, bir kırıntı gölgelendiricinin örnek kodu gösterilmektedir.

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

Nokta sprite'lerinin kullanılması, çizim basıncına göre kalınlığı ve gölgelendirmeyi değiştirmeyi kolaylaştırır. Böylece aşağıdaki gibi güçlü ve zayıf çizgiler ifade edilebilir:

İnce uçlu, keskin, eşit fırça darbesi.

Ortasına daha fazla basınç uygulanmış, keskin olmayan fırça darbesi.

Ayrıca, nokta sprite'lerini kullanan uygulamalar artık ayrı bir gölgelendirici kullanarak doku ekleyebilir. Bu sayede, kalem ve keçeli kalem gibi dokulara sahip fırçaların verimli bir şekilde gösterilmesi sağlanır.

Tarayıcıda ekran kalemi desteği

Dijital sanatçılar arasında dijital ekran kalemi kullanımı oldukça yaygın hale geldi. Modern tarayıcılar, kullanıcıların cihazlarında ekran kalemi kullanmalarına olanak tanıyan PointerEvent API'sini destekler: Kalemin basıncını ölçmek için PointerEvent.pressure, kalemin cihazla açısını ölçmek için PointerEvent.tiltX ve PointerEvent.tiltY kullanın.

Nokta imgesiyle fırça darbeleri uygulamak için PointerEvent interpolasyonu ve daha ayrıntılı bir etkinlik sırasına dönüştürülmesi gerekir. PointerEvent'te ekran kaleminin yönü kutupsal koordinatlar biçiminde elde edilebilir. Ancak pixiv Sketch, bunları kullanmadan önce ekran kaleminin yönünü temsil eden bir vektöre dönüştürür.

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

Birden fazla çizim katmanı

Katmanlar, dijital çizimdeki en benzersiz kavramlardan biridir. Kullanıcıların farklı resim parçalarını üst üste çizmesine ve katman katman düzenleme yapmasına olanak tanır. pixiv Sketch, diğer dijital çizim uygulamalarına benzer şekilde katman işlevleri sunar.

Geleneksel olarak, drawImage() ve kompozisyon işlemleri içeren birden fazla <canvas> öğesi kullanılarak katmanlar uygulanabilir. Ancak 2D tuval bağlamında, önceden tanımlanmış ve ölçeklenebilirliği büyük ölçüde sınırlayan CanvasRenderingContext2D.globalCompositeOperation kompozisyon modunu kullanmaktan başka seçenek olmadığından bu sorunludur. WebGL kullanılması ve gölgelendiricinin yazılması, geliştiricilerin API tarafından önceden tanımlanmamış beste modlarını kullanmalarına olanak tanır. Gelecekte pixiv Sketch, daha fazla ölçeklenebilirlik ve esneklik için WebGL'yi kullanarak katman özelliğini uygulayacaktır.

Katman kompozisyonu için örnek kod aşağıda verilmiştir:

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

Kova işleviyle büyük alan boyama

pixiv Sketch iOS ve Android uygulamalarında grup özelliği zaten mevcuttu ancak web sürümünde bu özellik yoktu. Paket işlevinin uygulama sürümü C++'da uygulandı.

C++'ta mevcut olan kod tabanı sayesinde pixiv Sketch, grup işlevini web sürümüne uygulamak için Emscripten ve asm.js'i kullandı.

bfsQueue.push(startPoint);

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

asm.js kullanılması, yüksek performanslı bir çözümün sunulmasını sağladı. Saf JavaScript'in yürütme süresi ile asm.js'nin yürütme süresi karşılaştırıldığında, asm.js'nin yürütme süresi %67 oranında kısaltılır. WASM kullanıldığında bu performansın daha da artması bekleniyor.

Test ayrıntıları:

  • Nasıl: Kova işleviyle 1180x800 piksellik alanı boyama
  • Test cihazı: MacBook Pro (M1 Max)

Yürütme süresi:

  • Saf JavaScript: 213,8 ms
  • asm.js: 70,3 ms

pixiv Sketch, Emscripten ve asm.js'yi kullanarak platforma özel uygulama sürümünden kod tabanını yeniden kullanarak grup özelliğini başarıyla yayınladı.

Çizim yaparken canlı yayın yapma

pixiv Sketch, pixiv Sketch LIVE web uygulaması üzerinden çizim yaparken canlı yayın yapma özelliği sunar. Bu özellik, getUserMedia() öğesinden alınan mikrofon ses parçasını ve <canvas> öğesinden alınan MediaStream video parçasını birleştiren WebRTC API'sini kullanır.

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

Sonuçlar

WebGL, WebAssembly ve WebRTC gibi yeni API'lerin gücüyle web platformunda karmaşık bir uygulama oluşturabilir ve bunu tüm cihazlarda ölçeklendirebilirsiniz. Bu örnek olayda tanıtılan teknolojiler hakkında daha fazla bilgiyi aşağıdaki bağlantılarda bulabilirsiniz: