Canvas.getContext и прямая работа с пикселями

В JavaScript объект <canvas> предоставляет низкоуровневый интерфейс для рисования графики. Основной способ взаимодействия с канвой — через метод getContext('2d'), который возвращает объект контекста рисования 2D. Этот контекст содержит все методы и свойства, необходимые для работы с графикой, от простых фигур до управления пикселями.


Получение контекста 2D

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

Метод getContext('2d') создаёт объект CanvasRenderingContext2D, который предоставляет интерфейс для рисования прямоугольников, линий, текста, а также для работы с изображениями и пикселями.


Основные методы рисования

Прямоугольники:

ctx.fillStyle = 'red'; // цвет заливки
ctx.fillRect(10, 10, 100, 50); // x, y, width, height

ctx.strokeStyle = 'blue'; // цвет обводки
ctx.strokeRect(10, 70, 100, 50);

Линии и кривые:

ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(150, 150);
ctx.stroke();

beginPath() начинает новый путь, moveTo(x, y) задаёт стартовую точку, lineTo(x, y) добавляет линию к пути, а stroke() рисует контур.

Кривые Безье:

ctx.beginPath();
ctx.moveTo(50, 200);
ctx.bezierCurveTo(70, 180, 130, 220, 150, 200);
ctx.stroke();

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) создаёт кубическую кривую с двумя контрольными точками.


Работа с изображениями и пикселями

Для прямой манипуляции пикселями используется объект ImageData, который хранит данные в формате RGBA (каждый пиксель — 4 байта: красный, зелёный, синий, альфа).

Получение и изменение пикселей:

// Считывание пикселей
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // Uint8ClampedArray

// Изменение всех пикселей: инверсия цветов
for (let i = 0; i < data.length; i += 4) {
    data[i] = 255 - data[i];     // красный
    data[i + 1] = 255 - data[i + 1]; // зелёный
    data[i + 2] = 255 - data[i + 2]; // синий
    // data[i + 3] — альфа, оставляем без изменений
}

// Применение изменений обратно на canvas
ctx.putImageData(imageData, 0, 0);

getImageData(x, y, width, height) возвращает пиксели указанной области, а putImageData(imageData, x, y) рисует их обратно на канву.


Работа с отдельными пикселями

Для доступа к конкретному пикселю используется индекс в массиве data:

function setPixel(x, y, r, g, b, a = 255) {
    const index = (y * canvas.width + x) * 4;
    data[index] = r;
    data[index + 1] = g;
    data[index + 2] = b;
    data[index + 3] = a;
}

// Пример: закрасить верхний левый пиксель красным
setPixel(0, 0, 255, 0, 0);
ctx.putImageData(imageData, 0, 0);

Важно учитывать, что массив data — одномерный, а каждый пиксель занимает 4 позиции: [R, G, B, A]. Индекс пикселя (x, y) вычисляется как (y * ширина + x) * 4.


Создание градиентов и паттернов на уровне пикселей

Прямой доступ к пикселям позволяет создавать собственные градиенты, шум или текстуры:

for (let y = 0; y < canvas.height; y++) {
    for (let x = 0; x < canvas.width; x++) {
        const value = Math.floor((x / canvas.width) * 255); // горизонтальный градиент
        setPixel(x, y, value, value, value, 255);
    }
}
ctx.putImageData(imageData, 0, 0);

Градиент создаётся вычислением значения каждого канала цвета для конкретной позиции.


Сравнение прямой работы с пикселями и стандартного рисования

  • Преимущества пиксельного уровня:

    • Полный контроль над каждым пикселем.
    • Создание эффектов, которые невозможно реализовать стандартными методами.
    • Возможность работы с изображениями и фильтрами.
  • Недостатки:

    • Более медленно для больших областей.
    • Требует ручного управления индексами и памятью.
  • Стандартные методы рисования:

    • Быстрое рисование геометрии.
    • Простая настройка стилей и линий.
    • Поддержка текста и изображений без явной работы с пикселями.

Примеры фильтров на Canvas

Серый фильтр:

for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = avg;
    data[i + 1] = avg;
    data[i + 2] = avg;
}
ctx.putImageData(imageData, 0, 0);

Прозрачность и альфа-композиция:

for (let i = 0; i < data.length; i += 4) {
    data[i + 3] = 128; // полупрозрачный
}
ctx.putImageData(imageData, 0, 0);

Эти приёмы позволяют создавать эффекты наложения и фильтров без использования внешних библиотек.


Итоговые рекомендации

  • Для динамических эффектов и интерактивных сцен часто выгодно комбинировать стандартные методы рисования и прямую работу с пикселями.
  • Всегда следить за размерами canvas и областями getImageData, чтобы не перегружать память.
  • Использовать requestAnimationFrame для анимации при манипуляциях на пиксельном уровне, чтобы сохранить производительность.