В данной статье будет рассмотрено управление изображением с помощью 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.
Результат:
из оригинала:
Предположим, мы хотим изменить альфа-канал и сделать изображение частично прозрачным. Тогда функция будет выглядеть:
function (r, g, b, a, factor) {
return [r, g, b, factor];
}
Здесь мы используем переменную factor, с помощью которой будем задавать прозрачность изображения. Значение этой переменной будет возвращаться как альфа-канал:
и более сложный пример. Здесь мы будем дополнительно задавать, к какой части изображения применить прозрачность:
function (r, g, b, a, factor, i) {
var total = this.original.data.length;
return [r, g, b, factor + 255 * (total - i) / total];
}
Если сделаем factor=111, получим:
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);
}
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];
}
Сепия
Простейший вариант: сделать серую версию и добавить немного цвета на неё – равного количества 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];
}
Вот другой вариант, который возможно и лучше, но мне не очень нравится:
Негатив
Вычтем значение каждого канала из 255 для получения негатива
function(r, g, b) {
return [255 - r, 255 - g, 255 - b, 255];
}
Шумы
Добавить шум. Это просто развлечение, берем случайное значение между –factor и factor и добавляем его к каждому каналу:
function(r, g, b, a, factor) {
var rand = (0.5 - Math.random()) * factor;
return [r + rand, g + rand, b + rand, 255];
}
Ваш ход
Сам пример опубликован по ссылке. Некоторые из манипуляций предлагает рассмотреть самостоятельно, используя исходный код и воображение.
В качестве шаблона берем следующее:
transformador.transform(function(r, g, b, a, factor, i) {
// здесь ваша магия...
return [r, g, b, a];
});
Попробуйте, например, сделать изображение черно-белым (не градациями серого, а черно-белым, где каждый пиксель либо 0,0,0, либо 255,255,255).
Или придумайте какой-то свой интересный пример.
Автор: luce_stellare