رسم تصویر در canvas

همانطور که از ابتدا انتظار داشتید، در canvas امکان رسم تصویر نیز وجود دارد. در واقع، این یکی از ویژگی‌های خوب و پرکاربرد canvas است که سرعت بالایی نیز دارد. در ادامه به روش رسم تصویر و دیگر موارد مربوط به تصویر در canvas می‌پردازیم. علت اینکه در این بخش با تصاویر زیادی مواجه نمی‌شوید، این است که رسم تصویر بسیار شبیه به رسم مستطیل است و اگر نکات گفته‌شده را رعایت کنید نتیجه‌ی درستی خواهید گرفت.

متد drawImage

با استفاده از این متد می‌توان یک تصویر در canvas رسم کرد. این متد سه حالت مختلف دارد که می‌توانید از هر حالت به دلخواه و بر اساس نیاز استفاده کنید. این متد در حالت‌های مختلف خود به ترتیب سه ورودی، پنج ورودی، و نه ورودی دریافت می‌کند. اگر ورودی‌هایی به جز این تعداد وارد این متد شوند با خطا مواجه خواهید شد. در کد زیر این متد بررسی شده است:


ctx.drawImage(img, x, y);
ctx.drawImage(img, x, y, width, height);

ctx.drawImage(img, cx, cy, cw, ch, ix, iy, iw, ih); 

ابتدا به دو حالت اول می‌پردازیم؛ حالت سوم نیاز به توضیحات بیشتری دارد. در حالت اول، تصویر با طول و عرض طبیعی و پیش‌فرض خود، در مختصات (x,y) رسم می‌شود. در حالت دوم، تصویر در مختصات (x,y) و به اندازه‌ی تعیین شده رسم می‌شود. در این حالت اگر width و height از اندازه‌ی تصویر بزرگتر باشند، کیفیت تصویر رسم شده کمتر خواهد بود.

در حالت سوم ابتدا مستطیلی به مختصات (cx,cy) و به اندازه‌ی cw در ch از روی تصویر انتخاب می‌شود. سپس این مستطیل به مختصات (ix,iy) و به اندازه‌ی iw در ih روی canvas رسم می‌شود. شکل زیر این موضوع را بهتر نمایش می‌دهد.

canvas drawImage

خب، این تمام کاری است که برای رسم تصویر در canvas لازم است! اما هنوز درباره‌ی ورودی اول این متد یعنی img حرفی نزده‌ایم. این ورودی می‌تواند هر نوع عنصر قابل نمایشی باشد. این ورودی می‌تواند یک عنصر canvas دیگر، یک ویدیو، یک تصویر از نوع jpg، png، webp یا حتی SVG باشد! البته مواردی هست که باید پیش از استفاده رعایت شوند و در ادامه به آن‌ها می‌پردازیم.

پیش از اجرای این متد، تصویر باید بارگیری (load) شده باشد!

انتخاب تصویر برای رسم

گفتیم ورودی اول باید یک عنصر نمایشی باشد، اما چندین روش مختلف برای انتخاب این عنصر نمایشی در جاوااسکریپت وجود دارد. دو روش اصلی، کلاس Image و دیگری انتخاب عنصر img موجود در صفحه است که جداگانه به هر یک می‌پردازیم.

رسم تصویر موجود در صفحه

منظور از تصویر موجود در صفحه، تصویری است که توسط عنصر img وارد صفحه شده باشد. برای رسم این نوع تصویر فقط کافیست عنصر موردنظر به درستی انتخاب شده و به عنوان ورودی اول قرار بگیرد. به نمونه کد زیر دقت کنید:


<img src="something.jpg" id="x_img" />

let x_img = document.getElementById("x_img");
ctx.drawImage(x_img, 100, 100, 300, 300);

رسم با کلاس Image

این کلاس نیز تفاوت چندانی با عنصر img درون صفحه ندارد. در واقع شئ ساخته شده با این کلاس نیز یک عنصر img است ولی درون صفحه قرار ندارد. برای رسم آن باید ابتدا به آن آدرس درست داده و پس از بارگیری آن، اقدام به رسم کنیم. برای نمونه به کد زیر دقت کنید:


let x_img = new Image;

x_img.onload = function () {
    ctx.drawImage(x_img, 100, 100, 300, 300);
}

x_img.src = "something.png";

علت اینکه مدیریت رویداد بارگیری پیش از تعیین آدرس تصویر نوشته شد، این است که برخی تصاویر از قبل بارگیری شده‌اند و وقتی آدرس آن‌ها تعیین می‌شود، رویداد load آن‌ها بلافاصله اجرا می‌شود. برای حل این مشکل، ابتدا مدیریت رویداد load تعریف می‌شود، سپس آدرس تصویر تعیین می‌شود.

رسم تصویر SVG

رسم تصاویر از نوع SVG در canvas نیز امکان‌پذیر است اما باید مواردی را رعایت کنیم تا در برخی مرورگر‌ها به مشکل برنخوریم. روش رسم تصاویری که درون سند DOM هستند، با تصاویری که خارج از آن هستند متفاوت است. توجه کنید که SVG موردنظر برای رسم باید استاندارد بوده و ویژگی‌های width، height، و viewBox مناسب داشته باشد. (ترجیحا بدون واحد باشند)

رسم SVG درون سند (inline SVG)

برای رسم SVG درون سند، ابتدا باید آن را تبدیل به یک تصویر با فرمت base64 کنیم و سپس آن را رسم کنیم. برای این کار فرآیندی که در کد زیر می‌بینید را اجرا می‌کنیم. توضیح این کد و روش کار آن خارج از حوصله‌ی این آموزش است اما در صورت علاقه می‌توانید «تبدیل تصاویر به فرمت base64» را جستجو کنید:


<svg width="1000" height="500" viewBox="0 0 1000 500" id="x_SVG">
    ...
</svg>

let x_svg = document.getElementById("x_SVG"),
    x_xml = (new XMLSerializer).serializeToString(x_svg),

    x_url = "data:image/svg+xml;base64," + btoa(x_xml),
    x_img = new Image;

x_img.onload = function () { ctx.drawImage(x_img, 0, 0, 500, 250); }
x_img.src = x_url;

تمام این فرآیند می‌تواند درون یک تابع انجام شود که یک عنصر SVG درون صفحه، و یک تابع می‌گیرد و آن را برای رسم در canvas آماده می‌کند. برای نمونه کد زیر این کار را به خوبی انجام می‌دهد:


function svg2img (svg, load) {
    let xml = (new XMLSerializer).serializeToString(svg),
        url = "data:image/svg+xml;base64," + btoa(xml),

        img = new Image;
    
    img.onload = load;
    img.src = url;
    
    return img;
}

رسم SVG خارجی

منظور از SVG خارجی، تصویری به صورت *.svg است. استاندارد بودن این نوع SVG، با SVG درون سند متفاوت است. مثلا این نوع از SVG باید ویژگی xmlns را داشته و ویژگی‌های href درون آن پیشوند xlink داشته باشند. موارد دیگری نیز هستند که باید رعایت شوند اما اگر از یک نرم‌افزار برای ایجاد تصاویر خود استفاده می‌کنید جای نگرانی نیست.

روش رسم این تصاویر ساده‌تر بوده و فقط کافیست یک شئ Image با آدرس آن ایجاد کرده و بعد از بارگیری آن را رسم کنید. به نمونه کد زیر دقت کنید:


let x_img = new Image;

x_img.onload = function () { ctx.drawImage(x_img, 100, 100, 300, 300); }
x_img.src = "something.svg";

رسم یک عنصر canvas دیگر

این روش نیز هیچ تفاوتی با رسم یک تصویر عادی درون سند ندارد. فقط کافیست عنصر canvas موردنظر را به عنوان ورودی اول به متد drawImage بدهیم. برتری این روش نسبت به دیگر روش‌ها در این است که می‌توان بدون ایجاد تغییر در شکل فعلی یا دیگر تنظیمات canvas، شکل مورد‌نظر را در آن رسم کرد.

این مورد می‌تواند در برنامه‌های سنگین، مانند یک بازی، بسیار مفید باشد، علاوه بر اینکه شکل فعلی عنصر دوم نیز می‌تواند مورد استفاده قرار بگیرد! در آینده بیشتر روی این مورد تمرکز خواهیم داشت. به نمونه کد زیر دقت کنید. در کد زیر یک شکل نسبتا پیچیده در یک canvas جانبی رسم می‌شود، سپس این تصویر در عنصر canvas اصلی ما رسم می‌شود:


let cvs = document.getElementById("cvs"),
    ctx = cvs.getContext("2d");

cvs.width = cvs.height = 500;

let img_cvs = (() => {
        let c = document.createElement("canvas");
        c.width = c.height = 150;
        
        c = c.getContext("2d");
        
        c.lineWidth = 20;
        c.arc(75, 75, 65, 0, Math.PI * 2);
        c.strokeStyle = "#CC0"; /* YELLOW */
        c.stroke();
        
        c.beginPath();
        
        c.lineWidth = 20;
        c.arc(75, 75, 30, 0, Math.PI * 2);
        c.strokeStyle = "#3c3"; /* GREEN */
        c.stroke();
        
        return c.canvas;
    }) ();

ctx.drawImage(img_cvs, 100, 100, 150, 150);

در کد بالا یک عنصر canvas ساخته شده و در متغیر img_cvs ذخیره می‌شود، سپس در عنصر cvs و در مختصات و اندازه‌ی تعیین‌شده رسم می‌شود. در برابر متغیر img_cvs یک تابع از نوع IIFE قرار دارد. این نوع توابع معمولا بدون نام هستند و بلافاصله بعد از تعریف شدن اجرا می‌شوند. درون این تابع یک عنصر canvas ساخته می‌شود، سپس ترسیماتی درون آن انجام می‌شود و در پایان به عنوان مقدار بازگشتی تابع تعیین می‌شود؛ یعنی پس از اجرای تابع، این عنصر canvas در متغیر img_cvs ذخیره می‌شود.

تنها مورد باقی‌مانده، کد c.canvas در آخرین خط تابع است. هر زمینه یک ویژگی به نام canvas دارد که به عنصر خود اشاره می‌کند. برای درک بهتر موضوع به کد زیر دقت کنید:


let cvs = document.getElementById("cvs"),
    ctx = cvs.getContext("2d");

ctx.canvas === cvs; /* true */

کدام روش بهتر است؟

اکنون که روش‌های مختلف انتخاب تصویر برای رسم در canvas بررسی شد، این پرسش به ذهن می‌آید که «کدام روش بهتر است؟» پاسخ این است که نمی‌توان با قاطعیت گفت یک روش از دیگر روش‌ها بهتر است. هر روشی مزایا و معایب خود را دارد و به نوع تصویر مورد استفاده نیز بستگی دارد. استفاده از کلاس Image باعث ارسال درخواست اضافی شده و باید منتظر بارگیری تصویر نیز بمانیم. از سوی دیگر عنصر img می‌تواند باعث شلوغ شدن سند شده یا روی مواردی مانند سئو اثر منفی بگذارد.

تجربه نشان داده که بهترین روش، روشی است که بهترین نتیجه را نسبت به کاری که می‌خواهیم انجام دهیم داشته باشد. بنابراین بهتر است به دنبال «بهترین» نباشیم و از تمام روش‌ها در جای خود استفاده کنیم.

متد toDataURL

این متد ارتباطی با رسم تصویر ندارد اما عدم اشاره به آن به نوعی کم لطفی است! این متد ترسیمات درون canvas را با فرمت و کیفیت دلخواه و به صورت base64 برمی‌گرداند. این متد دو ورودی می‌پذیرد که ورودی اول فرمت تصویر، و ورودی دوم کیفیت تصویر به صورت عددی بین 0 و 1 است. مقادیر پیش‌فرض به ترتیب “image/png” و 0.92  هستند.

برای دسترسی به این تصویر، باید یک شئ Image ساخت و این اطلاعات را به عنوان آدرس آن قرار داد. توجه کنید که این متد ویژه‌ی عنصر canvas است، و نه زمینه! به کد زیر دقت کنید. در کد زیر ترسیمات درون cvs تبدیل به تصویری با فرمت “image/jpeg” و کیفیت 1 شده و به عنوان یک تصویر در صفحه نمایش داده می‌شود:


let x_data = cvs.toDataURL("image/jpeg", 1),
    x_img = new Image;

x_img.src = x_data;
document.body.appendChild(x_img);

تغییر اطلاعات تصویر

شاید از خود بپرسید «پس چطور می‌توان روی تصاویر افکت ایجاد کرد یا آن‌ها را تغییر داد؟» پاسخ این است که برای این کار باید با کلاس ImageData آشنایی داشته باشید. این کلاس امکان بارگیری، بارگذاری، و تغییر تصاویر را به ما می‌دهد اما بررسی آن در این آموزش ممکن نیست و در آموزش‌های آینده به آن خواهیم پرداخت.

نتیجه‌گیری

در این بخش سعی کردیم توضیحات نسبتا کاملی درباره‌ی رسم تصویر در canvas ارائه دهیم. رسم تصویر یکی از موارد پرکاربرد در canvas است و اهمیت آن را در پروژه‌های بزرگ در آینده خواهید دید. در آموزش‌های بعدی به خط‌چین، سایه، طیف رنگ، و الگو می‌پردازیم.

حسین رفیعی

حسین رفیعی

طراحی و برنامه‌نویسی رو از وب شروع کردم و مثل خیلی از شماها آموزش‌های آقای سیدی خیلی بهم کمک کرد. هرچند این روزا تمرکز من روی برنامه‌نویسی خارج از وب هست ولی هنوز هم توی این فضا هستم و امیدوارم بتونم به بقیه کمک کنم!

سوال داری؟ برو به پنل پرسش و پاسخ

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *