La potenza del web per gli illustratori: in che modo pixiv utilizza le tecnologie web per la sua app di disegno

pixiv è un servizio online per la community che consente a illustratori ed appassionati di illustrazioni di comunicare tra loro tramite i contenuti. Consente alle persone di pubblicare le proprie illustrazioni. Hanno più di 84 milioni di utenti in tutto il mondo e più di 120 milioni di opere d'arte pubblicate a maggio 2023.

pixiv Sketch è uno dei servizi forniti da pixiv. Viene utilizzato per disegnare le opere d'arte sul sito web, con le dita o lo stilo. Supporta una varietà di funzionalità per disegnare illustrazioni fantastiche, tra cui numerosi tipi di pennelli, livelli e pittura a secchi. Inoltre, consente alle persone di riprodurre in streaming il proprio processo di disegno.

In questo case study, vedremo come pixiv Sketch ha migliorato le prestazioni e la qualità dell'app web utilizzando alcune nuove funzionalità della piattaforma web come WebGL, WebAssembly e WebRTC.

Perché sviluppare un'app per disegnare sul web?

pixiv Sketch è stato rilasciato per la prima volta sul web e su iOS nel 2015. Il pubblico di destinazione della versione web era principalmente il computer, che è ancora la piattaforma più usata dalla community delle illustrazioni.

Ecco i due motivi principali di Pixiv per scegliere di sviluppare una versione web anziché un'app desktop:

  • È molto costoso creare app per Windows, Mac, Linux e altri ancora. Il web raggiunge qualsiasi browser sul desktop.
  • Il web ha la migliore copertura su tutte le piattaforme. Il Web è disponibile su computer, dispositivi mobili e su ogni sistema operativo.

Tecnologia

pixiv Sketch offre agli utenti una serie di pennelli diversi tra cui scegliere. Prima di adottare WebGL, esisteva un solo tipo di pennello poiché la tela 2D era troppo limitata per rappresentare la complessa texture di diversi pennelli, come i bordi grezzi di una matita, e le differenze di larghezza e intensità del colore che variano in base alla pressione dello schizzo.

Tipi creativi di pennelli con WebGL

Tuttavia, con l'adozione di WebGL, è stato possibile aggiungere più varietà nei dettagli dei pennelli e aumentare il numero di pennelli disponibili a sette.

I sette diversi pennelli in pixel pixiv vanno da quelli più sottili a quelli più smussati, da nitidi a poco definiti, pixelati o lisci, e così via.

Utilizzando il contesto del canvas 2D, era possibile disegnare solo linee che hanno una trama semplice con larghezza distribuita in modo uniforme, come nello screenshot seguente:

Tratto di pennello con texture semplice.

Queste linee sono state disegnate creando percorsi e tratti, ma WebGL lo riproduce utilizzando sprite di punti e Shadr, mostrati nei seguenti esempi di codice

L'esempio seguente illustra uno strumento di Vertex Shaper.

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

L'esempio seguente mostra un codice di esempio per uno strumento di shadowing dei frammenti.

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

L'uso di sprite di punti semplifica la variazione di spessore e ombreggiatura in risposta alla pressione di disegno, consentendo di esprimere le seguenti linee forti e deboli, come queste:

Pennello nitido e uniforme con estremità sottili.

Pennello nitido con una maggiore pressione al centro.

Inoltre, le implementazioni che utilizzano sprite di punti possono ora applicare le texture utilizzando uno Shader separato, consentendo una rappresentazione efficiente dei pennelli con texture come matita e pennarello.

Supporto dello stilo nel browser

L'utilizzo di uno stilo digitale è diventato molto popolare tra gli artisti digitali. I browser moderni supportano l'API PointerEvent, che consente agli utenti di utilizzare uno stilo sul dispositivo. Utilizza PointerEvent.pressure per misurare la pressione della penna e PointerEvent.tiltX, PointerEvent.tiltY per misurare l'angolazione della penna rispetto al dispositivo.

Per eseguire pennellate con uno sprite di punti, è necessario interpolare PointerEvent e convertire in una sequenza di eventi più granulare. In PointerEvent, l'orientamento dello stilo può essere ottenuto sotto forma di coordinate polari, ma pixiv Sketch le converte in un vettore che rappresenta l'orientamento dello stilo prima di utilizzarlo.

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

Più livelli di disegno

I livelli sono uno dei concetti più unici nel disegno digitale. Consentono agli utenti di disegnare diverse parti dell'illustrazione l'uno sull'altro e di apportare modifiche livello per livello. pixiv Sketch offre funzioni di livello in modo molto simile ad altre app di disegno digitale.

Convenzionalmente, è possibile implementare i livelli utilizzando diversi elementi <canvas> con drawImage() e operazioni di compositing. Tuttavia, questo rappresenta un problema perché con il contesto di canvas 2D, non c'è altra scelta se non utilizzare la modalità di composizione CanvasRenderingContext2D.globalCompositeOperation, che è predefinita e limita ampiamente la scalabilità. Grazie all'utilizzo di WebGL e alla scrittura dello strumento di ombra, gli sviluppatori possono utilizzare modalità di composizione non predefinite dall'API. In futuro, pixiv Sketch implementerà la funzionalità dei livelli utilizzando WebGL per una maggiore scalabilità e flessibilità.

Ecco il codice di esempio per la composizione del livello:

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

Pittura di grandi aree con la funzione bucket

Le app pixiv Sketch per iOS e Android fornivano già la funzionalità del bucket, ma non la versione web. La versione app della funzione bucket è stata implementata in C++.

Con il codebase già disponibile in C++, pixiv Sketch ha utilizzato Emscripten e asm.js per implementare la funzione bucket nella versione web.

bfsQueue.push(startPoint);

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

L'utilizzo di asm.js ha attivato una soluzione ad alte prestazioni. Confrontando il tempo di esecuzione di JavaScript puro con quello di asm.js, il tempo di esecuzione con asm.js è ridotto del 67%. Questo dovrebbe migliorare ulteriormente con WASM.

Dettagli del test:

  • Come: colora un'area di 1180 x 800 px con la funzione bucket
  • Dispositivo di test:MacBook Pro (M1 Max)

Tempo di esecuzione:

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

Utilizzando Emscripten e asm.js, pixiv Sketch è riuscita a rilasciare la funzionalità dei bucket riutilizzando il codebase dalla versione dell'app specifica per la piattaforma.

Live streaming durante il disegno

pixiv Sketch offre la funzionalità di live streaming durante il disegno, tramite l'app web pixiv Sketch LIVE. Questa funzionalità utilizza l'API WebRTC, combinando la traccia audio del microfono ottenuta da getUserMedia() e la traccia video MediaStream recuperata dall'elemento <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());

Conclusioni

Grazie alla potenza di nuove API come WebGL, WebAssembly e WebRTC, puoi creare un'app complessa sulla piattaforma web e scalarla su qualsiasi dispositivo. Puoi scoprire di più sulle tecnologie introdotte in questo case study ai seguenti link: