Элементарные преобразования на изображениях в Canvas

Инверсия, поворот на 90 градусов, увеличение, основные операции с изображениями в HTML 5.

Для этих основных операций мы будем использовать функции, встроенные в расширение JavaScript для canvas, а также рутину, которая считывает содержимое пикселного изображения по пикселям и воспроизводит его в canvas. Эта функция, в отличие от встроенных функций, позволяет создавать собственные эффекты, особенно для цветов.
Например, можно снизить уровень прозрачности, начав слева направо, если ты хочешь слить одну картинку в другую, как это делается с Gimp. Это не тема этой статьи, но я бы все же уточнил, что это можно сделать, изменив значение альфа в цветовом коде RVBA (красный, зеленый, синий, альфа).

Ниже приводится изображение, которое мы будем использовать в качестве демонстрации для тестирования преобразований. Эти парусники на голубом море (медитеррейне) будут проходить всевозможные манипуляции функциями, код которых мы дадим.

Увеличение с помощью функции «Масштаб»

Функция scale позволяет изменить размер изображения, а следующая рутина показывает его использование: загружаем изображение в тег img, а затем изменяем отображение в теге canvas, указывая коэффициент увеличения. Все, что будет показано затем, будет показано с этим увеличением, и это будет применяться к функции drawImage, используемой для отображения нашего изображения. Размер тега canvas также определяется в зависимости от коэффициента увеличения .

<canvas id="canvasid"></canvas>
function transform(canvasid, filename, scale) {
  var viewCanvas = document.getElementById(canvasid);
  var viewCtx = viewCanvas.getContext("2d");
  var imageSource = new Image();
  imageSource.onload  = function () {
    viewCanvas.width = imageSource.width * scale;
    viewCanvas.height = imageSource.height  * scale;				
    viewCtx.scale(scale,scale);
    viewCtx.drawImage(imageSource, 0, 0);
  }
  imageSource.src = filename;
}

transform("canvasid", "image.jpg", 2);

Расширение с использованием алгоритма JavaScript

Мы хотим прочесть пикселное изображение и воспроизвести его таким же образом, увеличивая пикселы, поэтому мы должны сохранить оригинальное изображение во временном теге canvas, который не отображается. Второй тег используется для преобразованного изображения.
Функция drawImage отображает изображение в скрытом канале после загрузки. Функция getImage преобразует ее в матрицу точек, в которой можно получить доступ к пикселам по отдельности, что делает функция getColor. Затем строят свойство цвета в формате rgba и присваивают его прямоугольнику, который представляет элемент с заданным коэффициентом увеличения.

<canvas id="temporary" style="display:none"></canvas>
<canvas id="canvasid"></canvas>

function getColor(imgdata, x, y, width) {
  var index = (x + y * width) * 4;
  r = imgdata.data[index + 0];
  g = imgdata.data[index + 1];
  b = imgdata.data[index + 2];
  a = imgdata.data[index + 3];
  return 'rgba(' + r + ',' +  g + ',' +  b + ',' +  a + ')';
}

function transform(canvasid, filename, scale) {
  var i, j, it, jt;
  var tempCanvas = document.getElementById("temporary");
  var tempCtx = tempCanvas.getContext("2d");
  var viewCanvas = document.getElementById(canvasid);
  var ctxtarget = canvasTarget.getContext("2d");

  var imageSource = new Image();
  imageSource.onload  = function () {
    var w = imageSource.width;
    var h = imageSource.height;
    tempCanvas.width = w;
    tempCanvas.height = h;
    tempCtx.drawImage(imageSource, 0, 0, w, h);
    viewCanvas.width = w * scale;
    viewCanvas.height = h  * scale;
    var sourceData = tempCtx.getImageData(0, 0, w, h );
    ctxtarget.beginPath();
    for (i = 0; i < w; i++) {
      it = i * scale;
      for (j = 0; j < h; j++) {
        jt = j * scale;
        var color = getColor(sourceData, i, j, w);
        ctxtarget.fillStyle = color;
        ctxtarget.fillRect(it, jt, scale, scale);	
      }
    }
  }
  imageSource.src = filename;
}

transform("canvasid", "image.jpg", 2);

Качество не сильно отличается в данном случае, но это не всегда так. Как видно из сравнения инструментов супер-разрешения (ссылка внизу страницы), агоритм интерполяции, используемый canvas, может улучшить рендеринг. Но наша рутина позволяет контролировать, нужен ли каждый элемент изображения.

Горизонтальная инверсия

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

var it = 0;
for (i = w - 1; i >= 0; i--) {
  for (j = 0; j < h; j++) {
    ctxtarget.fillStyle = getColor(sourceData, it, j, w);
    ctxtarget.fillRect(i, j, 1, 1);	
  }
  it++;
}	

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

Горизонтальная инверсия со встроенными элементами

Нет функции flip в canvas, но тот же эффект можно получить, изменив способ отображения содержимого, что с функциями translate и scale.

function hflipBuiltin(canvasTarget, image, w, h) {
  canvasTarget.width = w;
  canvasTarget.height = h;
  var ctxtarget = canvasTarget.getContext("2d");   
  ctxtarget.translate(w, 0);
  ctxtarget.scale(-1, 1);
  ctxtarget.drawImage(image, 0, 0);
}

Код очень упрощен по сравнению с нашей функцией инверсии. Для начала отображения справа выполняется горизонтальное движение координат. Затем для отображения справа налево указано отрицательное значение. Остается только использовать функцию drawImage для отображения изображения.

Вертикальная инверсия

Чтобы извлечь выгоду из этой трансформации, необходимо, чтобы сам имидж был перевернут. Тогда можно построить сцену, начиная со дна, а не сверху вниз, как это делается по умолчанию. Для проведения игры, например, это было бы более интуитивно.

Заменяем петлю в предыдущем альго на эту :

for (i = 0; i < w; i++) {
  var jt = 0;
  for (j = h + 1; j >= 0; j--) {
    ctxtarget.fillStyle = getColor(sourceData, i, jt, w);
    ctxtarget.fillRect(i, j, 1, 1);	
    jt++;
  }
}	

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

ctxtarget.translate(0, h);
ctxtarget.scale(1, -1); 

Поворот на 90 ° по часовой стрелке

Расчет новых размеров упрощается в данном случае фигуры: в случае прямоугольника изменяют высоту и длину. Мы ничего не меняем на квадрат.

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

function rotate90(canvasTarget, image, w, h) {
  canvasTarget.width = h;
  canvasTarget.height = w;
  var ctxtarget = canvasTarget.getContext("2d");   
  ctxtarget.translate(h, 0);
  ctxtarget.rotate(Math.PI / 2);  
  ctxtarget.drawImage(image, 0, 0);  
}

Отметим, что для ширины канвы была назначена высота изображения, а для высоты канвы - ширина изображения.

При вращении изображение смещается. Для противодействия этому смещению выполняется горизонтальное перемещение, эквивалентное высоте изображения. Это подходит только для вращения на 90 ° .

Поворот на 90 ° в обратном направлении

На этот раз координатное перемещение происходит на основе ширины изображения. Используется та же функция, что и ранее, просто меняя эту строку :

ctxtarget.translate(0, w);

Полный исходный код всех этих функций плюс оболочка команд. Этот код может использоваться в проектах при условии сохранения уведомления об авторском праве.

Чтобы увеличить изображения перед их размещением в канвасе или вне канвы, инструменты реализуют удивительные алгоритмы, из которых вот подборка...