کلاس ImageData در canvas

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

کارکرد این کلاس چیست؟

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

در هنگام کار با متد‌های مربوط به این کلاس ممکن است خطای cross-origin دریافت کنید. این خطا هنگامی رخ می‌دهد که مثلا یک تصویر در canvas رسم شده باشد و سعی شود با این کلاس آن را به صورت اطلاعات عددی درآورد. این خطای امنیتی اجازه نمی‌دهد تصاویر کاربر به این روش دزدیده شوند. برای رفع این خطا می‌توانید تصاویر را به base64 تبدیل کرده و مستقیما درون کد از آن‌ها استفاده کنید. یا اینکه می‌توانید از یک سرور مجازی استفاده کنید.

متد createImageData

این متد یک شئ ImageData در ابعاد width و height ایجاد می‌کند. اگر یک شئ ImageData دیگر به عنوان ورودی این متد داده شود، یک شئ خالی با ابعاد آن شئ ساخته می‌شود:


let new_data = ctx.createImageData(width, height);
let new_data = ctx.createImageData(another_image_data);

متد getImageData

این متد اطلاعات یک بخش از canvas به مختصات (x,y) و در ابعاد width و height را به یک شئ ImageData تبدیل کرده و بازمی‌گرداند. نمای کلی این متد به صورت زیر است:


let my_image = ctx.createImageData(x, y, width, height);

متد putImageData

این متد اطلاعات یک شئ ImageData را در یک بخش از canvas به مختصات (x,y) قرار می‌دهد. نکته‌ی مهمی که باید به آن توجه کنید این است که این متد اطلاعات را شبیه به متد drawImage قرار نمی‌دهد، یعنی اگر یک شئ ImageData شامل یک تصویر کاملا شفاف درون canvas قرار دهید، بخش مورد نظر کاملا شفاف می‌شود! در حالی که اگر همان تصویر با متد drawImage رسم شود تغییری در canvas ایجاد نمی‌شود. این متد به صورت زیر است:


ctx.putImageData(my_image, x, y);

ویژگی‌های شئ ImageData

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

  • ماتریس تبدیلات روی این متد‌ها اثر نمی‌گذارد. مهم نیست چه نوع تبدیلاتی روی canvas اجرا کرده باشید، متد‌های getImageData و putImageData هیچ تاثیری از آن‌ها نمی‌پذیرند.
  • تغییر اندازه‌ی این نوع شئ امکان‌پذیر نیست. شئ ImageData هنگام ساخته شدن ویژگی‌های width و height را درون خود دارد و نمی‌توان اندازه‌ی آن را تغییر داد. تفاوت اصلی این شئ با تصویر نیز همین است. ابعاد این شئ ثابت هستند.

ساختار یک شئ ImageData

این شئ سه ویژگی دارد. ویژگی‌های width و height که پیش از این به آن‌ها اشاره شد و ابعاد تصویر درون شئ را در خود دارند. ویژگی سوم، و مهمترین ویژگی این شئ، ویژگی data است که یک آرایه به طول width * height * 4 است. این آرایه اطلاعات تصویر را دارد. درون این آرایه، اطلاعات هر پیکسل به ترتیب قرمز، سبز، آبی، و آلفا ذخیره شده‌اند.

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

ایجاد جلوه‌های تصویری

حال می‌خواهیم از این کلاس برای ایجاد جلوه‌های تصویری استفاده کنیم. از آنجایی که نمی‌توان یک تصویر را مستقیما تبدیل به شئ ImageData کرد، ابتدا تصویر خود را در یک canvas رسم کرده و با کمک متد getImageData آن را تبدیل می‌کنیم. توجه کنید که طول و عرض تصویر بهتر است بزرگ‌تر از 500 نباشد. علت آن را در انتهای آموزش خواهید فهمید. همچنین مراقب باشید که هنگام کار خطای cross-origin دریافت نکنید.

کد اولیه‌ی ما به صورت زیر است. در برنامه‌ی زیر یک تصویر به طول و عرض 500 در سمت چپ عنصر cvs رسم شده و اطلاعات آن به صورت یک شئ ImageData دریافت می‌شود، سپس اطلاعات این شئ توسط تابع change_img_data تغییر کرده و سپس در نیمه‌ی سمت راست عنصر cvs چاپ می‌شود. در ادامه کد‌های لازم را درون این برنامه قرار دهید تا اجرا شوند:


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

cvs.width = 1000;
cvs.height = 500;

let img = new Image,
    img_data;

img.onload = draw_image;
img.src = "flower.jpg";

function draw_image () {
    ctx.drawImage(img, 0, 0, 500, 500);
    img_data = ctx.getImageData(0, 0, 500, 500);

    change_img_data();
    ctx.putImageData(img_data, 500, 0);
}

function change_img_data () {
    let i, l;
    
    for (i = 0, l = img_data.width * img_data.height * 4; i < l; i += 4) {
        img_data.data[i + 0] /* R */
        img_data.data[i + 1] /* G */
        img_data.data[i + 2] /* B */
        img_data.data[i + 3] /* A */
    }
}

ابتدا باید به روش کار تابع change_img_data بپردازیم؛ کارکرد دیگر بخش‌های کد نیاز به توضیح خاصی ندارد. درون این تابع یک حلقه به طول آرایه‌ی img_data.data و با گام 4 ایجاد می‌شود. این حلقه به تمام پیکسل‌های شئ img_data دسترسی دارد و می‌تواند آن‌ها را تغییر دهد. همانطور که درون حلقه مشخص شده، می‌توان به هرکدام از رنگ‌های هر پیکسل دسترسی پیدا کرد و آن‌ها را تغییر داد.

در ادامه قرار است برای هر جلوه‌ی تصویری، یک تابع تعریف کنیم. پس بهتر است این تابع را باز هم تغییر دهیم تا بتوانیم از توابع تعریف‌شده بهتر و راحت‌تر استفاده کنیم. از آنجایی که کانال آلفا (شفافیت) در این جلوه‌ها تغییر نمی‌کند، از آن چشم‌پوشی می‌کنیم و فقط به رنگ‌ها می‌پردازیم. تابع change_img_data را به صورت زیر اصلاح کنید. در بخش مشخص‌شده با توضیحات، توابع موردنظر خود را قرار خواهیم داد:


function change_img_data () {
    let i, l, r, g, b;
    
    for (i = 0, l = img_data.width * img_data.height * 4; i < l; i += 4) {
        r = img_data.data[i + 0];
        g = img_data.data[i + 1];
        b = img_data.data[i + 2];
        
        /* --- */
        [r, g, b] = the_effect(r, g, b);
        /* --- */

        img_data.data[i + 0] = r;
        img_data.data[i + 1] = g;
        img_data.data[i + 2] = b;
    }
}

در کد بالا به جای the_effect نام توابعی که خواهیم نوشت را قرار دهید. البته فراموش نکنید که خود تابع را نیز به کد اضافه کنید. در ادامه برای بررسی توابع پیشرفته‌تر لازم خواهد بود که باز هم تابع change_img_data تغییر کند اما فعلا به این شکل آن را به کار ببرید.

طبق کد بالا، تابع the_effect، مستقل از اینکه چه نامی دارد، حداقل سه ورودی خواهد داشت و یک آرایه با سه عضو نیز به عنوان خروجی خواهد داشت که همان رنگ‌های پیکسل هستند که تغییرات روی آن‌ها اعمال شده است. همچنین خروجی‌های این تابع باید اعدادی صحیح بین 0 و 255 باشند.

جلوه‌ی سیاه و سفید (grayscale)

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


function grayscale (r, g, b) {
    let avg = (r + g + b) / 3 | 0;
    return [avg, avg, avg];
}

canvas image grayscale

سیاه و سفید جهت‌دار (vector grayscale)

می‌توان ایده‌ی بالا را گسترش داد و به جای گرفتن میانگین ساده‌ی رنگ‌ها، یک میانگین وزن‌دار از آن‌ها گرفت، به گونه‌ای که تاثیر برخی رنگ‌ها در این میانگین بیشتر یا کمتر باشد. نام «جهت‌دار» به این دلیل انتخاب شده که این میانگین می‌تواند شبیه به یک بردار سه‌بعدی در فضای rgb دیده شود! این تابع شش ورودی می‌پذیرد که سه ورودی دوم ضرایب میانگین (یا همان جهت بردار!) هستند. درون حلقه این میانگین را تغییر دهید تا بهتر متوجه کارکرد این جلوه شوید:


function vector_grayscale (r, g, b, x, y, z) {
    let c = (r * x + g * y + b * z) / (x + y + z) | 0;
    return [c, c, c];
}

canvas image vector grayscale

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


let x = Math.random(),
    y = Math.random(),
    z = Math.random();
    
/* for (...) */

[r, g, b] = vector_grayscale(r, g, b, x, y, z);

/* ... */

جلوه‌ی نگاتیو (negtive)

احتمالا مشابه این جلوه را در عکاسی دیده باشید. کد لازم برای ساخت این جلوه بسیار ساده و به صورت زیر است. کافیست تفاوت هر رنگ را از 255 (که رنگ سفید است) حساب کرده و به جای رنگ اصلی بگذاریم:


function negative (r, g, b) {
    return [255 - r, 255 - g, 255 - b];
}

canvas image negative

جلوه‌ی جا‌به‌جایی رنگ (color swap)

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


[r, g, b] = [b, g, r];

canvas image color swap

جلوه‌ی روشنایی (brightness)

این جلوه باعث روشن‌تر شدن تصویر می‌شود. تابع مربوط به این جلوه چهار ورودی می‌پذیرد که سه ورودی اول رنگ‌ها هستند و ورودی چهارم مقدار روشنایی است. کار این تابع ساده است و هر مقدار رنگ را با مقدار روشنایی جمع می‌کند، البته از آنجایی که ورودی چهارم می‌تواند منفی هم باشد، به کمک توابع min و max اطمینان حاصل می‌کند که مقدار رنگ در محدوده‌ی 0 و 255 باقی می‌ماند:


function brightness (r, g, b, s) {
    s = s | 0;
    
    r = Math.max(0, Math.min(255, r + s));
    g = Math.max(0, Math.min(255, g + s));
    b = Math.max(0, Math.min(255, b + s));

    return [r, g, b];
}

جلوه‌ی سیاه و سفید پله‌ای (quantize)

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

در خط اول، تابع بخش اعشاری n را حذف کرده و به کمک متد max اطمینان حاصل می‌کند که این عدد از 2 کوچکتر نباشد، زیرا نمی‌توان کمتر از دو طیف در یک تصویر داشت. دو عملگر ~~ شبیه به عملگر | 0 رفتار کرده و قسمت اعشاری را حذف می‌کنند. در ادامه میانگین سه رنگ محاسبه شده و در متغیر avg ذخیره می‌شود. این میانگین بین 0 و 255 نیست، بلکه بین 0 و 1 است.

در ادامه حاصل ضرب این مقدار در n+1 حساب شده، بخش صحیح آن تقسیم بر n-1 شده و کل عبارت در 255 ضرب می‌شود. این مقدار باید عددی بین 0 و 255 باشد که بنا به مقدار avg روی یکی از n پله‌ی بین 0 تا 255 قرار می‌گیرد. در نهایت قسمت صحیح این عدد (همراه با کنترل نهایی) اعمال شده و به عنوان خروجی قرار می‌گیرد:


function quantize (r, g, b, n) {
    n = Math.max(~~n, 2);

    let avg = (r + g + b) / (3 * 255),
        c = ~~(avg * (n + 1)) / (n - 1) * 255;
    
    c = Math.max(0, Math.min(255, ~~c));
    return [c, c, c];
}

canvas image quantize

جلوه‌های پیچیده‌تر

تاکنون تمام جلوه‌هایی که بررسی شدند فقط روی یک پیکسل متمرکز بودند. حال می‌خواهیم جلوه‌هایی را بررسی کنیم که نه تنها پیکسل فعلی، بلکه پیکسل‌های همسایه‌ی آن را نیز درگیر می‌کنند. برای کار با این جلوه‌ها لازم است که مختصات هر پیکسل را بدانید؛ بنابراین باید با دو تابع خاص آشنا شوید. در زیر دو تابع c2i و i2c نوشته شده‌اند. کار این توابع تبدیل مختصات به اندیس آرایه و بالعکس است. حرف c مخفف coordinates یا مختصات و حرف i نیز مخفف index یا اندیس است.


let c2i = (x, y, w) => (w * y + x) * 4,
    i2c = (i, w) => [((i / 4) % w), ~~(i / (w * 4))];

علاوه بر دو تابع بالا، لازم است با تابع جدیدی به نام convolute آشنا شویم. این تابع ویژه یک شئ ImageData و یک آرایه با 9 عضو به عنوان ماتریس ضرایب می‌پذیرد. شاید کد این تابع به نظر پیچیده برسد ولی تنها کاری که انجام می‌دهد، این است که مقدار رنگ پیکسل‌های همسایه را در ضریب درون ماتریس ضرب کرده و میانگین نتیجه‌ی نهایی را در پیکسل فعلی قرار می‌دهد. مقدار بازگشتی این تابع یک شئ ImageData است که نتیجه‌ی کار است:


function convolute (image_data, m) {
    let destination = ctx.createImageData(image_data),
        
        d = image_data.data,
        e = destination.data,

        w = image_data.width,
        h = image_data.height,

        x, y, i, j, c, t, n,
        l = m.reduce((a, b) => a + b),
        
        coords = [
            -1, -1, 0, -1, 1, -1,
            -1,  0, 0,  0, 1,  0,
            -1,  1, 0,  1, 1,  1
        ];
    
    for (x = 0; x < w; x++) {
        for (y = 0; y < h; y++) {
            n = c2i(x, y, w);

            for (i = 0; i < 3; i++) {
                t = 0;

                for (j = 0; j < 18; j += 2) {
                    c = d[c2i(x + coords[j], y + coords[j + 1], w) + i];
                    t += c * m[j / 2];
                }

                e[n + i] = Math.max(0, Math.min(255, t / l));
            }

            e[n + 3] = d[n + 3];
        }
    }

    return destination;
}

در کد تابع، به جای اینکه یک حلقه به اندازه‌ی طول آرایه‌ی اطلاعات ایجاد شود، دو حلقه درون هم به اندازه‌ی طول آرایه ایجاد شده و مقدار‌های x و y هرکدام جداگانه حساب می‌شوند. سپس این دو مقدار به کمک تابع c2i تبدیل به اندیس آرایه می‌شوند. درون این دو حلقه، دو حلقه‌ی کوچک دیگر وجود دارند که مقدار‌های رنگ پیکسل فعلی را با ترکیب رنگ از پیکسل‌های همسایه تعیین می‌کنند.

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

جلوه‌ی محو شدن (blur)

این جلوه تصویر را تار و به نوعی محو می‌کند. این جلوه هنگامی ایجاد می‌شود که پیکسل‌های همسایه به یک اندازه روی هر پیکسل اثر بگذارند، یعنی تمام درایه‌های ماتریس ضرایب 1 باشد. کد زیر ساختار این تابع را نشان می‌دهد:


function blur (image_data) {
    let matrix = [1, 1, 1,
                  1, 1, 1,
                  1, 1, 1];

    return convolute(image_data, matrix);
}

canvas image blur

جلوه‌ی تند کردن (sharpen)

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


function sharpen (image_data) {
    let matrix = [ 0, -1,  0,
                  -1,  5, -1,
                   0, -1,  0];

    return convolute(image_data, matrix);
}

canvas image sharpen

ترکیب چند جلوه

یک مزیت شئ ImageData این است که می‌توان اطلاعات درون آن را چندین بار تغییر داد و این یعنی می‌توان چندین جلوه‌ی مختلف روی یک تصویر اعمال کرد. برای نمونه می‌توانید ابتدا جلوه‌ی blur را پنج بار اجرا کرده و روی نتیجه، جلوه‌ی quantize را اجرا کنید.

canvas image complex effects

سرعت پایین

با وجود اینکه سرعت متد‌های getImageData و putImageData نسبتا بالاست، اما به طور کلی اعمال تغییرات روی یک شئ ImageData سرعت پایینی دارد. زیرا برای اعمال تغییر روی آن لازم است یک حلقه‌ی بزرگ ایجاد شود. جدا از اینکه چه پردازشاتی درون حلقه انجام می‌شود، پردازنده‌ی مرکزی (CPU) مجبور است درگیر آن شود و از آنجایی که پردازنده‌ی مرکزی فقط می‌تواند به تعداد کمی پردازش در لحظه رسیدگی کند، زمان زیادی صرف انجام دستورات حلقه می‌کند.

کافیست یک تصویر در ابعاد 2000 در 1000 به برنامه بدهید تا متوجه این موضوع شوید. هنگام اجرای برنامه، پردازنده با یک حلقه به طول 8000000 مواجه می‌شود و زمان نسبتا زیادی را صرف آن می‌کند و این باعث تاخیر برنامه می‌شود. مشکل از پردازنده‌ی مرکزی نیست، مشکل این است که ما از آن به اشتباه استفاده می‌کنیم! پردازنده‌ی مناسب برای انجام چنین کاری، پردازنده‌ی گرافیکی (GPU) است که می‌تواند پردازشات کوچک بسیاری را در یک لحظه انجام دهد.

این بدین معنی نیست که از اعمال جلوه‌های تصویری با کلاس ImageData خودداری کنید، بلکه فقط باید هنگام کار با آن مراقب باشید، زیرا پردازنده‌ی مرکزی برای انجام چنین کار‌هایی بهینه نیست. می‌توانید از تابع rAF به گونه‌ای استفاده کنید که بتوان گام‌های حلقه را در طول مثلا 5 ثانیه تقسیم‌بندی کرد و از فشار روی پردازنده جلوگیری کرد! راه‌های زیادی برای این کار وجود دارد که بنا بر رویکرد پروژه می‌تواند تغییر کند.

روش‌های جایگزین

حال که می‌دانیم تغییر دادن این نوع اشیاء همیشه مناسب نیست، بهتر است به دنبال روش‌های جایگزین باشیم. دو جایگزین بسیار مناسب برای این کار وجود دارند که هم سرعت بسیار بالایی دارند، و هم کار با آن‌ها نسبتا ساده‌است. پیش از این با یکی از این جایگزین‌ها آشنا شده‌ایم.

ویژگی globalCompositeOperation

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

هرچند این ویژگی می‌تواند در بسیاری از موارد کاربردی باشد، اما نمی‌تواند جلوه‌های پیچیده ایجاد کند. برای نمونه، ایجاد جلوه‌ی محو شدن یا تند کردن یا هر جلوه‌ی پیچیده‌ای که از ترکیب رنگ پیکسل‌های همسایه ایجاد می‌شود، با این ویژگی تقریبا غیر ممکن است. همچنین ایجاد جلوه‌های دیگر مانند سیاه و سفید پله‌ای بدون کمک کلاس ImageData نمی‌تواند انجام شود.

استفاده از WebGL

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

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

استفاده از filter در SVG

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

دیگر کاربرد‌های این کلاس

این کلاس فقط به ایجاد جلوه‌های تصویری محدود نیست بلکه کاربرد‌های آن فراتر از آن است. این کلاس می‌تواند تصاویر را به آرایه‌های اطلاعاتی تبدیل کند و این یعنی می‌توان تصاویر ایجاد‌شده در canvas را برای دیگران (یا سرور) ارسال کرد، و تصاویر را بدون نیاز به وجود عنصر تصویری ذخیره کرد.  همانطور که متوجه شده‌اید، اگر کاربرد‌های این کلاس فقط به ایجاد جلوه‌های تصویری محدود بود، هیچگاه خطای cross-origin ایجاد نمی‌شد!

پاک کردن لایه‌ی ترسیمات

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


function clear (context) {
    context.putImageData(context.createImageData(context.canvas.width, context.canvas.height), 0, 0);
}

حتی می‌توانیم برای سرعت بیشتر کار فقط یک شئ ImageData خالی ایجاد کرده و از آن برای پاک کردن استفاده کنیم؛ به این ترتیب دیگر لازم نیست با هر فراخوانی تابع clear یک شئ تازه ایجاد شود. به کمک این تابع جدید نه نیازی به تغییر تبدیلات هست، نه نیازی به تغییر رنگ یا ویژگی globalCompositeOperation و سرعت آن نیز بسیار بالاست.

تنها مشکل باقی‌مانده این است که اگر از متد clip استفاده شده باشد، فقط بخش‌های درون محدوده‌ی ترسیمات پاک خواهند شد. البته این مورد در تمام روش‌هایی که تاکنون بررسی کرده‌ایم نیز وجود دارد و راه‌حل آن این است که در استفاده از متد clip دقت کافی داشته باشید و همراه با آن از save و restore استفاده کنید.

البته توجه کنید، موقعیت‌هایی پیش می‌آید که در آن‌ها لازم است تبدیلات فعلی روی مساحت پاک‌شونده نیز اثر بگذارد. در این موارد استفاده از clearRect بهتر است، اما در مواردی که می‌خواهید کل لایه‌ی ترسیمات را پاک کنید، استفاده از تابع بالا بهتر است؛ همه‌چیز به برنامه‌ی نوشته‌شده بستگی دارد.

نتیجه‌گیری

در این بخش سعی کردیم ساختار یک شئ ImageData و رفتار آن را درک کرده و همچنین روش‌های ساخت چندین جلوه‌ی تصویری پرکاربرد را نیز بررسی کردیم. سعی کنید جلوه‌های ویژه‌ی خود را درون حلقه یا به کمک تابع convolute ایجاد کنید یا سعی کنید جلوه‌هایی که برای تصویر بالای پست استفاده شده را بازسازی کنید! در آموزش بعدی به کلاس Path2D و کاربردهای آن می‌پردازیم.

حسین رفیعی

حسین رفیعی

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

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

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