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

pixiv, çizerler ve illüstrasyon meraklılarının içerikleri üzerinden birbirleriyle iletişim kurmalarına yönelik bir online topluluk hizmetidir. Kullanıcıların kendi resimlerini yayınlamalarını sağlar. Dünya genelinde 84 milyondan fazla kullanıcısı ve Mayıs 2023 itibarıyla 120 milyondan fazla sanat eseri yayınladı.

pixiv Sketch, pixiv tarafından sağlanan hizmetlerden biridir. Web sitesindeki sanat eserlerini parmak veya ekran kalemiyle çizmek için kullanılır. Çok sayıda fırça, katman ve kova boyama gibi muhteşem resimler çizmek için çeşitli özellikleri destekler ve kullanıcıların çizim süreçlerini canlı olarak canlı yayınlamasına olanak tanır.

Bu örnek olayda, 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ğine göz atacağız.

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

pixiv Sketch ilk olarak 2015'te web'de ve iOS'te kullanıma sunuldu. Web sürümü için hedef kitlesi, resim topluluğu tarafından kullanılan en büyük platform olmaya devam eden masaüstü bilgisayardı.

Pixiv'in bir masaüstü uygulaması yerine bir web sürümü geliştirmeyi tercih etmesinin başlıca iki nedeni şunlardır:

  • Windows, Mac, Linux ve diğerleri için uygulama oluşturmak çok maliyetlidir. Web, masaüstündeki herhangi bir tarayıcıya ulaşır.
  • Web, tüm platformlarda en iyi erişime sahiptir. Web masaüstünde, mobil cihazda ve tüm işletim sistemlerinde kullanılabilir.

Teknoloji

pixiv Sketch, kullanıcıların aralarından seçim yapabileceği farklı fırçalar sunuyor. WebGL'yi kullanmaya başlamadan önce, 2D tuval, bir kalemin kaba kenarları ve eskiz basıncına göre değişen farklı genişlik ve renk yoğunluğu gibi farklı fırçaların karmaşık dokusunu gösteremeyecek kadar sınırlı olduğundan yalnızca tek bir fırça türü vardı.

WebGL kullanan fırça türleri

Ancak, WebGL'nin kullanılmasıyla, fırça ayrıntılarına daha fazla varyasyon ekleyip kullanılabilir fırçaların sayısını yediye çıkarmayı başardılar.

Pikselli yedi farklı fırça; ince, iri, keskin ve keskin olmayan, pikselleştirilmiş ve pürüzsüz gibi değişen fırçalardır.

2D tuval bağlamı kullanılarak, yalnızca aşağıdaki ekran görüntüsündeki gibi eşit olarak dağıtılmış genişliğe sahip basit bir dokuya sahip çizgiler çizmek mümkündür:

Basit dokulu fırça darbesi.

Bu çizgiler, yollar ve çizim çizgileri oluşturularak çizilmiştir; ancak WebGL, aşağıdaki kod örneklerinde gösterilen nokta imgeleri ve gölgelendiriciler kullanarak bunu yeniden oluşturur.

Aşağıdaki örnekte bir köşe 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 parça gölgelendirici 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 imgelerinin kullanımı, çizim baskısına göre kalınlığı ve gölgelendirmeyi değiştirmeyi kolaylaştırır ve aşağıdaki gibi güçlü ve zayıf çizgilerin ifade edilmesine olanak tanır:

İnce uçları olan, keskin ve eşit fırça darbesi.

Orta kısma daha fazla baskı uygulanan, keskin olmayan fırça darbesi.

Ayrıca, nokta sprite görselleri kullanan uygulamalar artık ayrı bir gölgelendirici kullanarak dokular ekleyebiliyor ve böylece fırçaların kurşun kalem ve keçeli kalem gibi dokularla verimli bir şekilde temsil edilmesine olanak tanıyor.

Tarayıcıda ekran kalemi desteği

Dijital ekran kalemi kullanmak dijital sanatçılar için son derece popüler hale geldi. 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 cihazla açısını ölçmek için ise PointerEvent.tiltX , PointerEvent.tiltY kullanın.

Nokta imgesiyle fırça darbeleri yapmak için PointerEvent için interpolasyon yapılması ve daha ayrıntılı bir etkinlik dizisine 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 birbirlerinin üzerine farklı çizimler çizmelerini ve katman katman düzenleme yapmalarına olanak tanır. pixiv Sketch, diğer dijital çizim uygulamalarının yaptığı gibi katman işlevleri sunar.

Geleneksel olarak, katmanları drawImage() ve birleştirme işlemleri ile birkaç <canvas> öğesi kullanarak uygulamak mümkündür. Ancak bu, 2D tuval bağlamı söz konusu olduğunda önceden tanımlanmış ve ölçeklenebilirliği büyük ölçüde sınırlayan CanvasRenderingContext2D.globalCompositeOperation beste modunu kullanmaktan başka bir yol olamayacağından bu durum sorunludur. WebGL kullanarak ve gölgelendiriciyi yazarak, geliştiricilerin API tarafından önceden tanımlanmış olmayan bileşim modlarını kullanmasına olanak tanır. Gelecekte pixiv Sketch, daha fazla ölçeklenebilirlik ve esneklik için WebGL özelliğini kullanarak katman özelliğini uygulayacaktır.

Katman bileşimi 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);
  }
}

Paket işleviyle geniş alan boyama

Pixiv Sketch iOS ve Android uygulamaları paket özelliğini zaten sunuyordu, ancak web sürümünde sağlamıyordu. Paket işlevinin uygulama sürümü C++'da uygulanmıştır.

pixiv Sketch, C++'ta halihazırda sunulan kod tabanıyla birlikte, paket 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ılması yüksek performanslı bir çözüm sağladı. Sadece JavaScript ve asm.js çalıştırma süreleri karşılaştırıldığında, asm.js kullanılarak yürütme süresi %67 oranında kısaltılmıştır. WASM kullanılırken bunun daha da iyi olması beklenmektedir.

Test ayrıntıları:

  • Nasıl: Paket işleviyle 1180x800 piksellik alanı 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 kullanarak paket özelliğini başarıyla kullanıma sundu.

Çizim yaparken canlı yayın yapma

pixiv Sketch, çizim sırasında pixiv Sketch LIVE web uygulaması aracılığıyla canlı yayın yapma özelliğini sunar. Bu özellik, getUserMedia() ürününden alınan mikrofon ses parçası ile <canvas> öğesinden alınan MediaStream video parçasını birleştirerek WebRTC API'sını 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ılardan edinebilirsiniz: