Kecanggihan web untuk ilustrator: Cara pixiv menggunakan teknologi web untuk aplikasi menggambar mereka

pixiv adalah layanan komunitas online bagi ilustrator dan penggemar ilustrasi untuk berkomunikasi satu sama lain melalui konten mereka. Hal ini memungkinkan orang-orang memposting ilustrasi mereka sendiri. Mereka memiliki lebih dari 84 juta pengguna di seluruh dunia, dan lebih dari 120 juta karya seni yang diposting per Mei 2023.

pixiv Sketch adalah salah satu layanan yang disediakan oleh pixiv. Alat ini digunakan untuk menggambar karya seni di {i>website<i}, menggunakan jari atau stilus. API ini mendukung berbagai fitur untuk menggambar ilustrasi menakjubkan, termasuk berbagai jenis kuas, lapisan, dan lukisan bucket, serta memungkinkan pengguna melakukan livestream proses menggambarnya.

Dalam studi kasus ini, kita akan melihat cara pixiv Sketch meningkatkan performa dan kualitas aplikasi web mereka dengan menggunakan beberapa fitur platform web baru seperti WebGL, WebAssembly, dan WebRTC.

Mengapa mengembangkan aplikasi sketsa di web?

pixiv Sketch pertama kali dirilis di web dan di iOS pada tahun 2015. Target audiens mereka untuk versi web utamanya adalah desktop, yang masih merupakan platform paling utama yang digunakan oleh komunitas ilustrasi.

Berikut adalah dua alasan utama pixiv untuk memilih mengembangkan versi web daripada aplikasi desktop:

  • Membuat aplikasi untuk Windows, Mac, Linux, dan lain-lain sangatlah mahal. Web menjangkau browser apa pun di desktop.
  • Web memiliki jangkauan terbaik di seluruh platform. Web tersedia pada desktop dan seluler, dan di setiap sistem operasi.

Teknologi

pixiv Sketch memiliki sejumlah kuas yang dapat dipilih oleh pengguna. Sebelum mengadopsi WebGL, hanya ada satu jenis kuas karena kanvas 2D terlalu terbatas untuk menggambarkan tekstur kompleks kuas yang berbeda, seperti tepi kasar pensil dan lebar serta intensitas warna yang berbeda yang berubah pada tekanan sketsa.

Jenis kuas kreatif menggunakan WebGL

Namun, dengan mengadopsi WebGL, mereka dapat menambahkan lebih banyak varietas dalam detail kuas dan meningkatkan jumlah kuas yang tersedia menjadi tujuh.

Tujuh kuas yang berbeda pada piksel mulai dari halus hingga kasar, tajam hingga tidak tajam, pecah hingga halus, dll.

Dengan menggunakan konteks kanvas 2D, Anda hanya dapat menggambar garis yang memiliki tekstur sederhana dengan lebar yang terdistribusi secara merata, seperti screenshot berikut:

Goresan kuas dengan tekstur sederhana.

Garis-garis ini digambar dengan membuat jalur dan menggambar goresan, tetapi WebGL mereproduksinya menggunakan sprite dan shader titik, yang ditunjukkan dalam contoh kode berikut

Contoh berikut menunjukkan shader verteks.

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

Contoh berikut menunjukkan kode contoh untuk shader fragmen.

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

Penggunaan point sprite memudahkan variasi ketebalan dan bayangan sebagai respons terhadap tekanan gambar, sehingga garis kuat dan lemah berikut diekspresikan, seperti ini:

Goresan kuas yang tajam dan rata dengan ujung tipis.

Goresan kuas yang tidak tajam dengan tekanan yang lebih besar di tengah.

Selain itu, implementasi yang menggunakan sprite titik kini dapat melampirkan tekstur dengan menggunakan shader terpisah, sehingga memungkinkan representasi kuas yang efisien dengan tekstur seperti pensil dan pulpen.

Dukungan stilus di browser

Penggunaan stilus digital telah menjadi sangat populer di kalangan seniman digital. Browser modern mendukung PointerEvent API yang memungkinkan pengguna menggunakan stilus di perangkat: Gunakan PointerEvent.pressure untuk mengukur tekanan pena, dan gunakan PointerEvent.tiltX, PointerEvent.tiltY untuk mengukur sudut pena terhadap perangkat.

Untuk melakukan sapuan kuas dengan sprite titik, PointerEvent harus diinterpolasi dan dikonversi menjadi urutan peristiwa yang lebih mendetail. Di PointerEvent, orientasi stilus dapat diperoleh dalam bentuk koordinat kutub, tetapi pixiv Sketch mengonversinya menjadi vektor yang merepresentasikan orientasi stilus sebelum menggunakannya.

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

Beberapa lapisan gambar

Lapisan adalah salah satu konsep yang paling unik dalam gambar digital. Alat ini memungkinkan pengguna menggambar berbagai potongan ilustrasi di atas satu sama lain, dan memungkinkan pengeditan lapisan demi lapisan. pixiv Sketch menyediakan fungsi lapisan seperti yang dilakukan aplikasi gambar digital lainnya.

Secara konvensional, lapisan dapat diimplementasikan menggunakan beberapa elemen <canvas> dengan drawImage() dan operasi komposisi. Namun, hal ini bermasalah karena dengan konteks kanvas 2D, tidak ada pilihan lain selain menggunakan mode komposisi CanvasRenderingContext2D.globalCompositeOperation, yang telah ditetapkan sebelumnya dan sebagian besar membatasi skalabilitas. Dengan menggunakan WebGL dan menulis shader, ini memungkinkan developer menggunakan mode komposisi yang belum ditentukan oleh API. Di masa mendatang, pixiv Sketch akan menerapkan fitur lapisan menggunakan WebGL untuk skalabilitas dan fleksibilitas yang lebih baik.

Berikut ini adalah kode contoh untuk komposisi lapisan:

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

Pengecatan area luas dengan fungsi bucket

Aplikasi pixiv Sketch iOS dan Android sudah menyediakan fitur bucket, tetapi versi web tidak. Versi aplikasi dari fungsi bucket diimplementasikan dalam C++.

Karena codebase sudah tersedia di C++, pixiv Sketch menggunakan Emscripten dan asm.js untuk menerapkan fungsi bucket ke dalam versi web.

bfsQueue.push(startPoint);

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

Penggunaan asm.js memungkinkan solusi berperforma tinggi. Membandingkan waktu eksekusi JavaScript murni dengan asm.js, waktu eksekusi yang menggunakan asm.js dipersingkat sebesar 67%. Hal ini diharapkan akan lebih baik saat menggunakan WASM.

Detail pengujian:

  • Bagaimana: Mengecat area berukuran 1180x800 piksel dengan fungsi bucket
  • Perangkat pengujian: MacBook Pro (M1 Max)

Waktu eksekusi:

  • JavaScript murni: 213,8 md
  • asm.js: 70,3 md

Dengan menggunakan Emscripten dan asm.js, pixiv Sketch berhasil merilis fitur bucket dengan menggunakan kembali codebase dari versi aplikasi khusus platform.

Live streaming sambil menggambar

pixiv Sketch menawarkan fitur untuk melakukan live stream saat menggambar, melalui aplikasi web pixiv Sketch LIVE. API ini menggunakan WebRTC API, yang menggabungkan trek audio mikrofon yang diperoleh dari getUserMedia() dan trek video MediaStream yang diambil dari elemen <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());

Kesimpulan

Dengan kecanggihan API baru seperti WebGL, WebAssembly, dan WebRTC, Anda dapat membuat aplikasi kompleks di platform web dan menskalakannya di perangkat apa pun. Anda dapat mempelajari lebih lanjut teknologi yang diperkenalkan dalam studi kasus ini melalui link berikut: