Манипуляция пикселями на холсте с использованием Javascript

В данной статье будет рассмотрено управление изображением с помощью Javascript и HTML тега canvas. Статья является переводом. Оригинал на phpied.com.

Управление пикселями


Простейший путь обрабатывать данные с изображения – это брать каждый пиксель и изменять значение одного или нескольких из его каналов: красный, зеленый, синий и альфа (прозрачность), для краткости будем называть их R, G, B и A.
Пример: изменим какие-нибудь значения, например поменяем B и G:
Было rgb(100, 50, 30, 255) станет rgb(100, 30, 50, 255)
Манипуляцию как таковую можно представить простейшей callback-функцией. Для приведенного выше примера:

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


Здесь мы игнорируем альфа-канал, он нам не нужен и будет установлен равным 255.

Результат:

image

из оригинала:

image

Предположим, мы хотим изменить альфа-канал и сделать изображение частично прозрачным. Тогда функция будет выглядеть:

function (r, g, b, a, factor) {
  return [r, g, b, factor];
}


Здесь мы используем переменную factor, с помощью которой будем задавать прозрачность изображения. Значение этой переменной будет возвращаться как альфа-канал:

image

и более сложный пример. Здесь мы будем дополнительно задавать, к какой части изображения применить прозрачность:

function (r, g, b, a, factor, i) {
  var total = this.original.data.length;
  return [r, g, b, factor + 255 * (total - i) / total];
}


Если сделаем factor=111, получим:

image
this ссылается на созданный нами объект, который можно было немного увидеть. Он хранит в себе кое-какую нужную информацию, которая может пригодиться, как рассмотренном примере.

Холст


Рассмотрим структуру нашего холста. Начнем с конструктора:

function CanvasImage(canvas, src) {
  // загрузка изображения на холст
  var context = canvas.getContext('2d');
  var i = new Image();
  var that = this;
  i.onload = function(){
    canvas.width = i.width;
    canvas.height = i.height;
    context.drawImage(i, 0, 0, i.width, i.height);
    // запомним оригинальные пикселы
    that.original = that.getData();
  };
  i.src = src;
  // кешируем
  this.context = context;
  this.image = i;
}


Используем это, чтоб передать ссылку на элемент холста, находящийся где-нибудь на странице, а также url изображения.

Изображение должно быть в том же домене, где идет обработка его данных:

var transformador = new CanvasImage(
  $('canvas'),
  '/wp-content/uploads/2008/05/zlati-nathalie.jpg'
);


Конструктор создает объект new Image, и после загрузки изображение отрисовывается на холсте. Затем сохраняем некоторые вещи на будущее такие как context, объект image и оригинальные данные об изображении. ‘this’ – тот же самый, к которому манипулятор пикселями имеет доступ в примере выше.

Далее используем 3 простых метода для установки, получения и сброса данных изображения с холста:

CanvasImage.prototype.getData = function() {
  return this.context.getImageData(0, 0, this.image.width, this.image.height);
};
CanvasImage.prototype.setData = function(data) {
  return this.context.putImageData(data, 0, 0);
};
CanvasImage.prototype.reset = function() {
  this.setData(this.original);
}


Мозг всей обработки – это метод transform(). Он обрабатывает callback-вызов манипулятора пикселями и factor, который в сущности является конфигурационной настройкой для манипулятора. Затем он проходит по всем пикселям, передает значение olddata rgba-канала в callback-функцию и использует вернувшиеся значения как newdata. В конце newdata записывается на холст.

CanvasImage.prototype.transform = function(fn, factor) {
  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var res = [];
  var len = newpx.length;
  for (var i = 0; i < len; i += 4) {
   res = fn.call(this, oldpx[i], oldpx[i+1], oldpx[i+2], oldpx[i+3], factor, i);
   newpx[i]   = res[0]; // r
   newpx[i+1] = res[1]; // g
   newpx[i+2] = res[2]; // b
   newpx[i+3] = res[3]; // a
  }
  this.setData(newdata);
};


Довольно просто, не так ли? Единственный смущающий момент должен быть инкремент i+=4 в цикле. Данные возвращаются через getImageData().data как массив с 4 элементами для каждого пикселя.

Предположим, у изображения есть всего 2 пикселя: красный и синий, и нет прозрачности. Тогда данные для этого изображения выглядят как:

[
  255, 0, 0, 255,
  0, 0, 255, 255
]


Callback


Дальнейший код просто показывает различные варианты callback-функции, и создает UI для их использования. Рассмотрим несколько из них:

Градации серого

Градации серого – это равное количество красного, синего, зеленого. Самый простой способ добиться этого – посчитать среднее значение:
var agv = (r + g + b) / 3;
Этого вполне достаточно. Но есть секретная формула для обработки фотографий с людьми, она устанавливает разную чувствительность для каналов:

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg, avg, avg, 255];
}


image

Сепия

Простейший вариант: сделать серую версию и добавить немного цвета на неё – равного количества rgb для каждого пикселя. Я добавил 100 красного, 50 зеленого, но вы можете выбрать другие значения.

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg + 100, avg + 50, avg, 255];
}


image

Вот другой вариант, который возможно и лучше, но мне не очень нравится:

image

Негатив

Вычтем значение каждого канала из 255 для получения негатива

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


image

Шумы

Добавить шум. Это просто развлечение, берем случайное значение между –factor и factor и добавляем его к каждому каналу:
function(r, g, b, a, factor) {
  var rand =  (0.5 - Math.random()) * factor;
  return [r + rand, g + rand, b + rand, 255];
}

image

Ваш ход


Сам пример опубликован по ссылке. Некоторые из манипуляций предлагает рассмотреть самостоятельно, используя исходный код и воображение.
В качестве шаблона берем следующее:

transformador.transform(function(r, g, b, a, factor, i) {
  // здесь ваша магия...
  return [r, g, b, a];
});


Попробуйте, например, сделать изображение черно-белым (не градациями серого, а черно-белым, где каждый пиксель либо 0,0,0, либо 255,255,255).

Или придумайте какой-то свой интересный пример.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 8

Only users with full accounts can post comments. Log in, please.