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

pixiv, içerik üreticilerin ve illüstrasyon meraklılarının içerikleri aracılığıyla birbirleriyle iletişim kurabildiğ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ısı ve Mayıs 2023 itibarıyla 120 milyondan fazla sanat eseri var.

pixiv Sketch, pixiv tarafından sunulan hizmetlerden biridir. Parmak veya kalem kullanarak web sitesinde çizim yapmak için kullanılır. Çeşitli fırça türleri, katmanlar ve dolgu boyama gibi birçok özellik sayesinde muhteşem çizimler yapmanıza olanak tanır. Ayrıca, çizim sürecinizi canlı yayınlamanıza da imkan verir.

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

Neden web'de bir eskiz uygulaması geliştirmelisiniz?

pixiv Sketch ilk olarak 2015'te web'de ve iOS'te yayınlandı. Web sürümünün hedef kitlesi öncelikli olarak masaüstü kullanıcılarıydı. Masaüstü, illüstrasyon topluluğu tarafından en çok kullanılan platform olmaya devam ediyor.

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 işletim sistemleri için uygulama oluşturmak çok maliyetlidir. Web, masaüstündeki herhangi bir tarayıcıya ulaşır.
  • Web, platformlar arasında en iyi erişime sahiptir. Web, masaüstü ve mobil cihazlarda, tüm işletim sistemlerinde kullanılabilir.

Teknoloji

pixiv Sketch, kullanıcıların seçebileceği çeşitli fırçalar sunar. WebGL'yi kullanmadan önce, 2D tuval farklı fırçaların karmaşık dokusunu (ör. kurşun kalemin kaba kenarları ve çizim basıncına göre değişen genişlik ve renk yoğunluğu) tasvir etmek için çok sınırlı olduğundan yalnızca bir tür fırça vardı.

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

Ancak WebGL'nin kullanıma sunulmasıyla birlikte fırça ayrıntılarına daha fazla çeşitlilik ekleyebildiler ve kullanılabilir fırça sayısını yediye çıkardılar.

pixiv'deki yedi farklı fırça (ince, kalın, keskin, bulanık, pikselli, düz vb.)

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

Basit dokulu fırça darbesi.

Bu çizgiler, yollar oluşturularak ve konturlar çizilerek oluşturulmuştur. Ancak WebGL, bunu aşağıdaki kod örneklerinde gösterildiği gibi nokta sprite'ları ve gölgelendiriciler kullanarak yeniden üretir.

Aşağıdaki örnekte bir köşe gölgelendiricisi 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 parça gölgelendiricisi için örnek kod 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'ların kullanılması, çizim basıncına yanıt olarak kalınlık ve gölgelendirmeyi kolayca değiştirmenizi sağlar. Bu sayede, aşağıdaki gibi güçlü ve zayıf çizgiler oluşturabilirsiniz:

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

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

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

Tarayıcıda ekran kalemi desteği

Dijital sanatçılar arasında dijital kalem kullanımı oldukça yaygınlaştı. Modern tarayıcılar, kullanıcıların cihazlarında ekran kalemi kullanmasına olanak tanıyan PointerEvent API'yi destekler: Kalem basıncını ölçmek için PointerEvent.pressure, kalemin cihaza göre açısını ölçmek için PointerEvent.tiltX ve PointerEvent.tiltY kullanın.

Nokta sprite ile fırça darbeleri yapmak için PointerEvent değerinin enterpolasyon yapılması ve daha ayrıntılı bir etkinlik dizisine dönüştürülmesi gerekir. PointerEvent'te, kalemin yönü kutup koordinatları şeklinde elde edilebilir ancak pixiv Sketch, bunları kullanmadan önce kalemin 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ı çizim parçalarını üst üste çizmelerine ve katman katman düzenlemeler yapmalarına olanak tanır. pixiv Sketch, diğer dijital çizim uygulamalarına benzer şekilde katman işlevleri sunar.

Geleneksel olarak, <canvas> ve drawImage() öğeleriyle kompozisyon işlemleri 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 bileşim modunu kullanmaktan başka seçenek olmadığından bu durum sorunludur. WebGL'yi kullanarak ve gölgelendiriciyi yazarak geliştiricilerin API tarafından önceden tanımlanmamış kompozisyon modlarını kullanmasına olanak tanır. Gelecekte pixiv Sketch, daha fazla ölçeklenebilirlik ve esneklik için WebGL kullanarak katman özelliğini uygulayacak.

Aşağıda katman oluşturma için örnek kod 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 bir alanı boyama

pixiv Sketch'in iOS ve Android uygulamalarında zaten kova özelliği vardı ancak web sürümünde bu özellik yoktu. Bucket işlevinin uygulama sürümü C++'ta uygulanmıştır.

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

bfsQueue.push(startPoint);

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

asm.js kullanılarak yüksek performanslı bir çözüm elde edildi. Saf JavaScript ile asm.js'nin yürütme süresi karşılaştırıldığında, asm.js kullanılarak yürütme süresinin %67 kısaldığı görülür. Bu durumun WASM kullanılırken daha da iyi olması bekleniyor.

Test ayrıntıları:

  • Nasıl: 1.180 x 800 piksel alanı kova işleviyle boyayın.
  • 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 kullanarak platforma özel uygulama sürümündeki kod tabanını yeniden kullanıp kova özelliğini başarıyla yayınladı.

Çizim yaparken canlı yayın

pixiv Sketch, pixiv Sketch LIVE web uygulaması aracılığıyla çizim yaparken canlı yayın yapma özelliği sunar. Bu özellik, getUserMedia() öğesinden alınan mikrofon ses parçası ile <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 herhangi bir cihazda ölçeklendirebilirsiniz. Bu örnek olayda tanıtılan teknolojiler hakkında daha fazla bilgiyi aşağıdaki bağlantılarda bulabilirsiniz: