Изменение размера изображения с помощью JavaScript canvas (плавно)


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

вот моя скрипка:http://jsfiddle.net/EWupT/

  var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    canvas.width=300
    canvas.height=234
    ctx.drawImage(img, 0, 0, 300, 234);
    document.body.appendChild(canvas);

первый-это обычный тег изображения с измененным размером, а второй-холст. Обратите внимание, как холст один не так гладко. Как я могу достичь "гладкость"?

6 60

6 ответов:

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

(обновление в спецификации было добавлено свойство качества,imageSmoothingQuality который в настоящее время доступен только в Chrome.)

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

Bi-linear использует пиксели 2x2 для интерполяции, а bi-cubic использует 4x4, поэтому, делая это шагами, вы можете приблизиться к bi-cubic результату при использовании билинейной интерполяции, как видно на полученных изображениях.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

В зависимости от того, насколько радикальным является ваш размер, вы можете пропустить Шаг 2, если разница меньше.

в демо вы можете увидеть новый результат теперь очень похож на элемент изображения.

Я создал многоразовый угловой сервис для обработки высококачественного изменения размеров изображений / холстов для всех, кто заинтересован:https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

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

пример использования:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})

С скрипка Трунг Ле Нгуен Ната совсем не правильно (он просто использует исходное изображение на последнем шаге)
Я написал свою собственную общую скрипку с сравнением производительности:

скрипка

в основном это:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

Я создал библиотеку, которая позволяет снизить любой процент при сохранении всех данных о цвете.

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

этот файл можно включить в браузер. Результаты будут выглядеть как photoshop или image magick, сохраняя все цветовые данные, усредняя пиксели, а не принимая близлежащие и отбрасывая другие. Он не использует формулу, чтобы угадать средние, он принимает точное среднее.

основываясь на ответе K3N, я переписываю код вообще для всех, кто хочет

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

ОБНОВЛЕНИЕ JSFIDDLE DEMO

вот мой ОНЛАЙН ДЕМО

Я написал небольшую JS-утилиту для обрезки и изменения размера изображения на интерфейсе. Вот это ссылке на проекте GitHub. Также вы можете получить blob из окончательного изображения, чтобы отправить его.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;