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

pixiv è un servizio di community online che consente a illustratori e appassionati di illustrazioni di comunicare tra loro tramite i propri 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 partire da maggio 2023.

pixiv Sketch è uno dei servizi forniti da pixiv. Viene utilizzato per disegnare artwork sul sito web utilizzando le dita o gli stili. Supporta una serie di funzionalità per disegnare illustrazioni straordinarie, tra cui numerosi tipi di pennelli, livelli e pittura con secchiello, e consente inoltre alle persone di trasmettere in live streaming il processo di disegno.

In questo caso studio, vedremo in che modo pixiv Sketch ha migliorato le prestazioni e la qualità della propria app web utilizzando alcune nuove funzionalità della piattaforma web come WebGL, WebAssembly e WebRTC.

Perché sviluppare un'app di schizzi 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 costituito da utenti di computer, che rappresentano ancora la piattaforma più utilizzata dalla community di illustratori.

Ecco i due motivi principali per cui pixiv ha scelto di sviluppare una versione web anziché un'app desktop:

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

Tecnologia

Pixiv Sketch offre una serie di diversi pennelli tra cui gli utenti possono scegliere. Prima di adottare WebGL, esisteva un solo tipo di pennello poiché la tela 2D era troppo limitata per rappresentare la trama complessa di diversi pennelli, come i bordi ruvidi di una matita e la larghezza e l'intensità del colore diverse che cambiano in base alla pressione dello schizzo.

Tipi di pennelli creativi che utilizzano WebGL

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

I sette diversi pennelli di pixiv, da fini a grossolani, da nitidi a sfocati, da pixelati a lisci e così via.

Utilizzando il contesto della tela 2D, era possibile disegnare solo linee con una trama semplice con larghezza distribuita uniformemente, come nello screenshot seguente:

Pennata con trama semplice.

Queste linee sono state disegnate creando percorsi e tratti, ma WebGL lo riproduce utilizzando sprite e shader a punti, come mostrato negli esempi di codice riportati di seguito

L'esempio seguente mostra un vertex shader.

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 un frammento shader.

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 degli sprite a punti consente di variare facilmente lo spessore e l'ombreggiatura in risposta alla pressione del tratto, consentendo di esprimere le seguenti linee forti e deboli, ad esempio:

Tratto netto e uniforme con estremità sottili.

Tratto di pennello non nitido con maggiore pressione applicata al centro.

Inoltre, le implementazioni che utilizzano sprite a punti ora possono allegare texture utilizzando un shader separato, consentendo una rappresentazione efficiente dei pennelli con texture come matite e pennarelli.

Supporto per stilo sul browser

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

Per eseguire tratti con un punto sprite, PointerEvent deve essere interpolato e convertito in una sequenza di eventi più granulare. In PointerEvent, l'orientamento del pennino può essere ottenuto sotto forma di coordinate polari, ma Pixiv Sketch lo converte in un vettore che rappresenta l'orientamento del pennino 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 sovrapporre diversi elementi di illustrazione e di apportare modifiche livello per livello. pixiv Sketch offre funzioni di livello molto simili a quelle di altre app di disegno digitale.

Tradizionalmente, è possibile implementare i livelli utilizzando diversi elementi <canvas> con operazioni di drawImage() e composizione. Tuttavia, questo è problematico perché, con il contesto della tela 2D, non c'è altra scelta che utilizzare la modalità di composizione CanvasRenderingContext2D.globalCompositeOperation, che è predefinita e limita notevolmente la scalabilità. L'utilizzo di WebGL e la scrittura dello shader consentono agli sviluppatori di 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à.

Di seguito è riportato il codice di esempio per la composizione dei livelli:

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 aree di grandi dimensioni con la funzione secchio

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

Con la base di codice già disponibile in C++, pixiv Sketch ha utilizzato Emscripten e asm.js per implementare la funzione del 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 consentito di implementare una soluzione efficiente. Se confrontiamo il tempo di esecuzione di JavaScript puro con asm.js, il tempo di esecuzione utilizzando asm.js viene ridotto del 67%. Il risultato dovrebbe essere ancora migliore con l'utilizzo di WASM.

Dettagli del test:

  • Come: dipingi un'area di 1180 x 800 pixel 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à del bucket riutilizzando la base di codice della versione dell'app specifica per la piattaforma.

Live streaming durante il disegno

pixiv Sketch offre la funzionalità di live streaming mentre disegni, tramite l'app web Pixiv Sketch LIVE. Questa 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. Per saperne di più sulle tecnologie introdotte in questo caso di studio, visita i seguenti link: