pixiv یک سرویس اجتماعی آنلاین برای تصویرگران و علاقه مندان به تصویرسازی است تا از طریق محتوای خود با یکدیگر ارتباط برقرار کنند. این به افراد امکان می دهد تصاویر خود را پست کنند. آنها بیش از 84 میلیون کاربر در سراسر جهان دارند و بیش از 120 میلیون اثر هنری تا می 2023 ارسال شده است.
pixiv Sketch یکی از خدمات ارائه شده توسط pixiv است. برای کشیدن آثار هنری در وب سایت، با استفاده از انگشتان یا قلم استفاده می شود. از ویژگیهای مختلفی برای ترسیم تصاویر شگفتانگیز از جمله انواع برسها، لایهها و نقاشی سطلی پشتیبانی میکند و همچنین به افراد اجازه میدهد تا روند طراحی خود را به صورت زنده پخش کنند.
در این مطالعه موردی، نگاهی خواهیم داشت به اینکه چگونه pixiv Sketch عملکرد و کیفیت برنامه وب خود را با استفاده از برخی ویژگیهای جدید پلتفرم وب مانند WebGL، WebAssembly و WebRTC بهبود بخشید.
چرا یک برنامه طراحی در وب توسعه دهید؟
pixiv Sketch برای اولین بار در سال 2015 در وب و iOS منتشر شد. مخاطبان هدف آنها برای نسخه وب در درجه اول دسکتاپ بود که هنوز هم اصلی ترین پلت فرم مورد استفاده جامعه تصویرسازی است.
در اینجا دو دلیل اصلی pixiv برای انتخاب توسعه یک نسخه وب به جای یک برنامه دسکتاپ آورده شده است:
- ایجاد اپلیکیشن برای ویندوز، مک، لینوکس و غیره بسیار پرهزینه است. وب به هر مرورگر روی دسکتاپ دسترسی پیدا می کند.
- وب بهترین دسترسی را در بین پلتفرم ها دارد. وب روی دسکتاپ و موبایل و در هر سیستم عاملی در دسترس است.
تکنولوژی
pixiv Sketch تعدادی براش مختلف برای انتخاب کاربران دارد. قبل از استفاده از WebGL، تنها یک نوع قلم مو وجود داشت، زیرا بوم دوبعدی برای به تصویر کشیدن بافت پیچیده براش های مختلف، مانند لبه های درشت یک مداد و عرض و شدت رنگ متفاوت که در فشار طرح تغییر می کند، بسیار محدود بود.
انواع خلاقانه براش ها با استفاده از WebGL
با این حال، با پذیرش WebGL، آنها توانستند انواع بیشتری را در جزئیات قلم مو اضافه کنند و تعداد براش های موجود را به هفت برس افزایش دهند.
با استفاده از بافت بوم دوبعدی، فقط میتوان خطوطی را ترسیم کرد که بافتی ساده با عرض یکنواخت دارند، مانند تصویر زیر:
این خطوط با ایجاد مسیرها و کشیدن خطوط ترسیم شده اند، اما WebGL این را با استفاده از sprites و 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;
}
مثال زیر نمونه کد برای یک قطعه سایه زن را نشان می دهد.
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);
}
استفاده از اسپرایتهای نقطهای، تغییر ضخامت و سایهزنی را در پاسخ به فشار کششی ساده میسازد، و اجازه میدهد خطوط قوی و ضعیف زیر مانند موارد زیر بیان شوند:
علاوه بر این، پیادهسازیهایی که از اسپریتهای نقطهای استفاده میکنند، اکنون میتوانند با استفاده از یک سایهزن مجزا، بافتها را به هم متصل کنند و امکان نمایش کارآمد برسها با بافتهایی مانند مداد و قلم نمدی را فراهم کنند.
پشتیبانی از قلم در مرورگر
استفاده از قلم دیجیتال برای هنرمندان دیجیتال بسیار محبوب شده است. مرورگرهای مدرن از PointerEvent API پشتیبانی میکنند که به کاربران امکان میدهد از قلم روی دستگاه خود استفاده کنند: از PointerEvent.pressure
برای اندازهگیری فشار قلم استفاده کنید و از PointerEvent.tiltX
، PointerEvent.tiltY
برای اندازهگیری زاویه قلم با دستگاه استفاده کنید.
برای انجام حرکات قلم مو با نقطه اسپرایت، PointerEvent
باید درون یابی شود و به یک توالی رویداد با دانه بندی ریزتر تبدیل شود. در PointerEvent، جهت گیری قلم را می توان به شکل مختصات قطبی به دست آورد، اما pixiv Sketch آنها را قبل از استفاده به یک برداری تبدیل می کند که جهت گیری قلم را نشان می دهد.
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);
}
چندین لایه طراحی
لایه ها یکی از منحصر به فردترین مفاهیم در طراحی دیجیتال هستند. آنها به کاربران اجازه میدهند تکههای مختلف تصویر را روی هم بکشند و اجازه ویرایش لایه به لایه را میدهند. pixiv Sketch عملکردهای لایه را مانند سایر برنامه های طراحی دیجیتال ارائه می دهد.
به طور معمول، پیاده سازی لایه ها با استفاده از چندین عنصر <canvas>
با drawImage()
و عملیات ترکیبی امکان پذیر است. با این حال، این مشکل ساز است زیرا در زمینه بوم دوبعدی، چاره ای جز استفاده از حالت ترکیب CanvasRenderingContext2D.globalCompositeOperation
وجود ندارد، که از پیش تعریف شده است و تا حد زیادی مقیاس پذیری را محدود می کند. با استفاده از WebGL و نوشتن سایهزن، به توسعهدهندگان اجازه میدهد از حالتهای ترکیبی استفاده کنند که توسط API از پیش تعریف نشدهاند. در آینده، pixiv Sketch ویژگی لایه را با استفاده از WebGL برای مقیاسپذیری و انعطافپذیری بیشتر پیادهسازی خواهد کرد.
در اینجا کد نمونه برای ترکیب لایه آمده است:
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);
}
}
نقاشی منطقه بزرگ با عملکرد سطل
برنامههای pixiv Sketch iOS و Android قبلاً ویژگی bucket را ارائه میکردند، اما نسخه وب این کار را نکرد. نسخه برنامه تابع سطل در C++ پیاده سازی شد.
با کدپایه در حال حاضر در C++ موجود است، pixiv Sketch از Emscripten و asm.js برای پیاده سازی تابع سطل در نسخه وب استفاده کرد.
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
استفاده از asm.js یک راه حل عملکردی را فعال کرد. با مقایسه زمان اجرای جاوا اسکریپت خالص در مقابل asm.js ، زمان اجرا با استفاده از asm.js 67% کوتاه شده است. انتظار می رود در هنگام استفاده از WASM این حتی بهتر باشد.
جزئیات تست:
- چگونه: منطقه 1180x800 پیکسل را با عملکرد سطل رنگ کنید
- دستگاه تست: MacBook Pro (M1 Max)
زمان اجرا:
- جاوا اسکریپت خالص: 213.8 میلیثانیه
- asm.js: 70.3 میلیثانیه
با استفاده از Emscripten و asm.js ، pixiv Sketch توانست با استفاده مجدد از پایگاه کد از نسخه برنامه مخصوص پلتفرم، ویژگی سطل را با موفقیت آزاد کند.
پخش زنده هنگام طراحی
pixiv Sketch این ویژگی را برای پخش زنده در حین طراحی، از طریق برنامه وب pixiv Sketch LIVE ارائه می دهد. این از WebRTC API استفاده میکند که آهنگ صوتی میکروفون بهدستآمده از getUserMedia()
و تراک ویدیوی MediaStream
بازیابی شده از عنصر <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());
نتیجه گیری
با قدرت API های جدید مانند WebGL، WebAssembly و WebRTC، می توانید یک برنامه پیچیده در پلتفرم وب ایجاد کنید و آن را در هر دستگاهی مقیاس دهید. در لینک های زیر می توانید با فناوری های معرفی شده در این مطالعه موردی بیشتر آشنا شوید:
- WebGL
- همچنین WebGPU ، جانشین WebGL را بررسی کنید
- WebAssembly
- WebRTC
- مقاله اصلی به زبان ژاپنی