Змінення розміру зображення на полотні HTML5


314

Я намагаюся створити мініатюрне зображення на стороні клієнта за допомогою javascript та елемента полотна, але коли я скорочую зображення, це виглядає жахливо. Схоже, він був зменшений у Photoshop із перестановкою, встановленою на "Найближчий сусід" замість Bicubic. Я знаю, що можна зробити так, щоб це виглядало правильно, адже цей сайт може робити це добре, використовуючи і полотно. Я намагався використовувати той самий код, який вони роблять, як показано у посиланні "[Джерело]", але це все ще виглядає жахливо. Чи щось мені не вистачає, якусь установку, яку потрібно встановити, чи щось?

Редагувати:

Я намагаюся змінити розмір jpg. Я спробував змінити розмір того ж jpg на пов'язаному сайті та у фотошопі, і він виглядає чудово, коли його зменшують.

Ось відповідний код:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

Здається, я помилявся, пов’язаний веб-сайт не робив кращої роботи щодо зменшення іміджу. Я спробував інші запропоновані методи, і жоден з них не виглядає краще. До цього призвели різні методи:

Photoshop:

alt текст

Полотно:

alt текст

Зображення з рендерінгом зображення: оптимізуйте набір якості та масштабуйте з шириною / висотою:

alt текст

Зображення з рендерінгом зображення: оптимізуйте набір якості та масштабуйте за допомогою -moz-перетворення:

alt текст

Розмір розміру полотна на піксастичному:

alt текст

Я думаю, це означає, що Firefox не використовує двобічну вибірку, як це належить. Мені просто доведеться почекати, поки вони насправді додадуть.

EDIT3:

Оригінальне зображення


Чи можете ви опублікувати код, який ви використовуєте для зміни розміру зображення?
Хаві

Ви намагаєтесь змінити розмір зображення GIF або подібного зображення з обмеженою палітрою? Навіть у фотошопі ці зображення не змінюються масштабно, якщо ви не конвертуєте їх у RGB.
leepowers

Чи можете ви опублікувати копію оригінального зображення?
Хаві

Змінення розміру зображення за допомогою javascript трохи невдало - ви не тільки використовуєте клієнтську обробну потужність для зміни розміру зображення, але й робите це під час кожного завантаження сторінки. Чому б просто не зберегти зменшену версію з Photoshop і подати її замість / у тандемі з оригінальним зображенням?
визначається

29
Оскільки я роблю завантажувач зображень із можливістю зміни розміру та обрізання зображень перед завантаженням.
Теланор

Відповіді:


393

Отже, що робити, якщо всі браузери (насправді Chrome 5 видав мені досить хороший) не забезпечать вам достатньо хорошої якості перекомпонування? Ви реалізуєте їх тоді самі! Ну давай, ми вступаємо в нову епоху Web 3.0, сумісних із браузерами HTML5, супер оптимізованих компіляторів JavaScript JIT, багатоядерних (†) машин, з тоннами пам’яті, чого ти боїшся? Гей, у javascript є слово java, тож це повинно гарантувати продуктивність, правда? Ось, мініатюрний код, що генерує:

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

... за допомогою яких можна отримати такі результати!

img717.imageshack.us/img717/8910/lanczos358.png

так що все-таки, ось "виправлена" версія вашого прикладу:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

Тепер прийшов час подати кращі веб-переглядачі там і побачити, який з них, найменш ймовірно, підвищить артеріальний тиск у вашого клієнта!

Гм, де мій ярлик сарказму?

(оскільки багато частин коду базується на генераторі галереї Anrieff, чи він також охоплюється GPL2? я не знаю)

насправді через обмеження JavaScript, багатоядерність не підтримується.


2
Я насправді сам намагався реалізувати це, виконуючи так само, як і ви, копіюючи код з редактора зображень з відкритим кодом. Оскільки я не зміг знайти жодної твердої документації щодо алгоритму, мені було важко оптимізувати його. Врешті-решт, у мене було трохи повільно (знадобилося кілька секунд, щоб змінити розмір зображення). Коли я отримаю можливість, я спробую ваш і побачу, чи швидше це. І я думаю, що веб-працівники вже зараз можуть зробити багатоядерний JavaScript. Я збирався спробувати їх використати, щоб пришвидшити це, але у мене виникли проблеми з розумінням, як перетворити це в багатопотоковий алгоритм
Теланор,

3
Вибачте, забув це! Я відредагував відповідь. Це все одно не буде швидко, двостулкові повинні бути швидшими. Не кажучи вже про використаний нами алгоритм - це не звичайне двостороннє зміна розміру (яке є лінією за рядком, горизонтальною, а потім вертикальною), тому це цикл повільніше.
syockit

5
Ви приголомшливі і заслуговуєте тонни дивовижних.
Rocklan

5
Це дає гідні результати, але в останній версії Chrome ... 7,4 секунди для зображення 1,8 МП
mpen

2
Як такі способи вдається досягти такої високої оцінки ?? Показане рішення повністю не враховує логарифмічну шкалу, яка використовується для зберігання кольорової інформації. RGB 127,127,127 - це чверть яскравості 255, 255, 255, а не половина. Відбір проб вниз в розчині призводить до затемнення зображення. Ганьба, це закрито, оскільки існує дуже простий і швидкий метод зменшити розмір, який дає навіть кращі результати, ніж Photoshop (ОП, мабуть, налаштування встановили неправильно), наведений зразок
Blindman67

37

Алгоритм швидкого зміни розміру / повторного зображення за допомогою фільтра Hermite з JavaScript. Підтримує прозорість, дає хорошу якість. Попередній перегляд:

введіть тут опис зображення

Оновлення : версія GitHub додана версія 2.0 (швидше, веб-працівники + передавані об'єкти). Нарешті я отримав це працює!

Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}

Можливо, ви можете включити посилання на демонстрацію miniPaint та рефінансування Github?
syockit

1
Чи поділитесь також версією веб-працівників? Можливо, через налаштування накладних витрат, для невеликих зображень це повільніше, але це може бути корисно для більш великих вихідних зображень.
syockit

додані демо, git-посилання, також багатоядерна версія. До речі, я не витратив надто багато часу на оптимізацію багатоядерної версії ... Єдину версію, я вважаю, оптимізовано добре.
ViliusL

Величезна різниця та гідне виконання. Дуже дякую! до і після
KevBurnsJr

2
@ViliusL Ага, я згадав, чому веб-працівники не так добре працюють. Раніше вони не мали спільної пам’яті, і досі її немає! Можливо, коли-небудь, коли їм вдасться розібратися, ваш код прийде до використання (це, а може, замість цього люди використовують PNaCl)
syockit

26

Спробуйте pica - це дуже оптимізований резизер з вибірними алгоритмами. Дивіться демонстрацію .

Наприклад, розмір оригінального зображення з першого повідомлення змінено на 120 мс з фільтром Ланцоса та вікном 3 пікселя або на 60 м з фільтром Box та вікном 0,5 пікселя. Для величезного розміру зображення 17 Мб, розмір 5000x3000 пікселів займає ~ 1 секунди на робочому столі та 3 на мобільному пристрої.

Всі принципи зміни розміру були дуже добре описані в цій темі, і pica не додає ракетній науці. Але він дуже добре оптимізований для сучасних JIT-систем, і готовий до використання поза коробкою (через npm або bower). Крім того, він використовує веб-працівників, коли вони доступні, щоб уникнути зависань інтерфейсу.

Я також планую незабаром додати непостійну підтримку маски, оскільки це дуже корисно після зниження масштабу.


14

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

Ось код, який змінює розмір зображення щоразу, коли ви перезавантажуєте зображення. Я знаю, що це взагалі не є оптимальним, але я надаю це як доказ концепції.

Також вибачте за те, що використовуєте jQuery для простих селекторів, але я просто відчуваю себе занадто комфортно із синтаксисом.

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

Моя функція createImage викликається один раз, коли документ завантажується, і після цього він викликається кожен раз, коли вікно отримує подію зміни розміру.

Я протестував це в Chrome 6 і Firefox 3.6, обидва на Mac. Ця "техніка" їсть процесор так, ніби влітку морозиво, але це робить свою справу.


9

Я створив кілька алгоритмів для інтерполяції зображень на піксельних масивах HTML canvas, які можуть бути корисні тут:

https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

Вони можуть бути скопійовані / вставлені і можуть бути використані всередині веб-працівників для зміни розміру зображень (або будь-якої іншої операції, яка вимагає інтерполяції - я зараз їх використовую для визначення зображень).

Я не додав вищезгадані матеріали про ланкцо, тому сміливо додайте це як порівняння, якщо хочете.


6

Це функція javascript, адаптована з коду @ Telanor. При передачі зображення base64 в якості першого аргументу функції він повертає base64 зміненого зображення. maxWidth та maxHeight необов’язкові.

function thumbnail(base64, maxWidth, maxHeight) {

  // Max size for thumbnail
  if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
  if(typeof(maxHeight) === 'undefined') var maxHeight = 500;

  // Create and initialize two canvas
  var canvas = document.createElement("canvas");
  var ctx = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");

  // Create original image
  var img = new Image();
  img.src = base64;

  // Determine new ratio based on max size
  var ratio = 1;
  if(img.width > maxWidth)
    ratio = maxWidth / img.width;
  else if(img.height > maxHeight)
    ratio = maxHeight / img.height;

  // Draw original image in second canvas
  canvasCopy.width = img.width;
  canvasCopy.height = img.height;
  copyContext.drawImage(img, 0, 0);

  // Copy and resize second canvas to first canvas
  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;
  ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);

  return canvas.toDataURL();

}

ваш підхід дуже швидко , але він виробляє нечітке зображення, ви можете побачити тут: stackoverflow.com/questions/18922880 / ...
confile

6

Я б радимо вам перевірити це посилання та переконайтесь, що воно встановлено як істинне.

Контроль поведінки масштабування зображень

Представлено в Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 представив властивість mozImageSmoothingEnabled до елемента полотна; якщо це булеве значення помилкове, зображення не буде згладжено при масштабуванні. Ця властивість за замовчуванням відповідає дійсності. переглянути звичайний відбиток?

  1. cx.mozImageSmoothingEnabled = false;

5

Якщо ви просто намагаєтесь змінити розмір зображення, я рекомендую налаштування widthта heightзображення за допомогою CSS. Ось короткий приклад:

.small-image {
    width: 100px;
    height: 100px;
}

Зауважте, що heightі widthможна також встановити за допомогою JavaScript. Ось швидкий зразок коду:

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

Крім того, щоб переконатися, що розмір зображення виглядає добре, додайте такі правила css до селектора зображень:

Наскільки я можу сказати, усі браузери, крім IE, використовують бікубічний алгоритм, щоб змінити розмір зображень за замовчуванням, тому ваші розміри зображень повинні виглядати добре у Firefox та Chrome.

Якщо установка CSS widthі heightне працює, ви можете грати з допомогою CSS transform:

Якщо з будь-якої причини вам потрібно використовувати полотно, зауважте, що існують два способи зміни розміру зображення: зміна розміру полотна css або малювання зображення меншим розміром.

Дивіться це питання для більш детальної інформації.


5
Ані зміна розміру полотна, ані нанесення зображення на менший розмір не вирішує проблему (в Chrome).
Нестор

1
Chrome 27 створює приємні розміри зображення, але ви не можете скопіювати результат на полотно; намагаючись зробити це, скопіюйте оригінальне зображення замість цього.
syockit

4

Для зміни розміру зображення шириною, меншою за оригінальну, я використовую:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

і працює =).


4

Я отримав це зображення, натиснувши правою кнопкою миші елемент полотна у firefox та збереживши як.

alt текст

var img = new Image();
img.onload = function () {
    console.debug(this.width,this.height);
    var canvas = document.createElement('canvas'), ctx;
    canvas.width = 188;
    canvas.height = 150;
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';

так що все-таки, ось "виправлена" версія вашого прикладу:

var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);

var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body

document.body.appendChild(canvasCopy);

var copyContext = canvasCopy.getContext("2d");

img.onload = function()
{
        var ratio = 1;

        // defining cause it wasnt
        var maxWidth = 188,
            maxHeight = 150;

        if(img.width > maxWidth)
                ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
                ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        // the line to change
        // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        // the method signature you are using is for slicing
        ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};

// changed for example
img.src = 'original.jpg';

Я намагався робити те, що ти робив, і це не виходило так добре, як твоє. Якщо я щось не пропустив, єдиною вашою зміною було використання підпису методу масштабування замість нарізки, правда? Чомусь це не працює для мене.
Теланор

3

Проблема деяких із цих рішень полягає в тому, що вони отримують доступ безпосередньо до даних пікселів і проходять цикл через них, щоб виконати зменшення кемпінгу. Залежно від розміру зображення це може бути дуже ресурсомістким, і було б краще використовувати внутрішні алгоритми браузера.

Функція drawImage () використовує метод лінійної інтерполяції, найближчого сусіда перекомпонування. Це добре працює, якщо ви не змінюєте розмір більше половини початкового розміру .

Якщо ви хочете змінити розмір максимум на половину за один раз, результати будуть досить хорошими та набагато швидшими, ніж доступ до даних пікселів.

Ця функція зменшується до половини за один раз, поки не досягне бажаного розміру:

  function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );

Подяки на цю посаду


2

Тож щось цікаве, що я знайшов деякий час тому, працюючи з полотном, що може бути корисним:

Щоб самостійно змінити розмір управління полотном, вам потрібно використовувати атрибути height=""та width=""(або canvas.width/canvas.height ) елементи. Якщо ви використовуєте CSS для зміни розміру полотна, він фактично розтягне (тобто: зміни розміру) вміст полотна, щоб відповідати повному полотну (а не просто збільшувати чи зменшувати площу полотна.

Варто спробувати спробувати намалювати зображення на полотні з атрибутами висоти та ширини, встановленими на розмір зображення, а потім за допомогою CSS змінити розмір полотна до потрібного розміру. Можливо, для цього був би використаний інший алгоритм зміни розміру.

Слід також зазначити, що полотно має різні ефекти в різних браузерах (і навіть різних версіях різних браузерів). Алгоритми та методи, які використовуються в браузерах, швидше за все, змінюватимуться з часом (особливо це стосується Firefox 4 та Chrome 6, які вийдуть настільки скоро, що зробить великий акцент на ефективності візуалізації полотна).

Крім того, ви, можливо, захочете також зробити SVG, оскільки він, ймовірно, також використовує інший алгоритм.

Удачі!


1
Встановлення ширини чи висоти полотна за допомогою атрибутів HTML призводить до очищення полотна, тому не можна проводити будь-які зміни розміру за допомогою цього методу. Також SVG призначений для роботи з математичними зображеннями. Мені потрібно вміти малювати PNG та інше, щоб звичайно мені не допомогти.
Теланор

Налаштування висоти та ширини полотна та зміна розміру за допомогою CSS не допомагає, я знайшов (у Chrome). Навіть якщо змінити розмір, використовуючи -webkit-перетворення, а не CSS-ширина / висота, інтерполяція не виходить.
Нестор

2

У мене є відчуття, що написаний нами модуль дасть подібні результати для Photoshop, оскільки він зберігає кольорові дані шляхом усереднення їх, не застосовуючи алгоритм. Це щось повільно, але для мене це найкраще, тому що він зберігає всі кольорові дані.

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

Він не бере найближчого сусіда і не скидає інші пікселі, або вибирає групу і приймає випадкове середнє значення. Він займає точну пропорцію, яку кожен вихідний піксель повинен виводити в піксель призначення. Середній піксельний колір у джерелі буде середнім кольором пікселів у пункті призначення, яким ці інші формули, я думаю, їх не буде.

приклад того, як користуватися, знаходиться внизу https://github.com/danschumann/limby-resize

ОНОВЛЕННЯ ОКТЯБРЯ 2018 : Ці дні мій приклад є більш академічним, ніж будь-що інше. Webgl - це майже 100%, тому вам краще змінити розмір, щоб отримати подібні результати, але швидше. PICA.js робить це, я вважаю. -


1

Я перетворив відповідь @ syockit, а також поступовий підхід на багаторазову службу Angular для всіх, хто цікавиться: https://gist.github.com/fisch0920/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
    })
})

1

Швидкий і простий розмір зображень Javascript:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

Історія

Це дійсно після багатьох раундів досліджень, читання та спроб.

Алгоритм resizer використовує сценарій Hermite @ ViliusL (resizeer Hermite дійсно найшвидший і дає досить хороший результат). Розширений з необхідними функціями.

Форкс 1 працівник повинен змінити розмір, щоб він не заморожував ваш веб-переглядач під час зміни розміру, на відміну від усіх інших JS-резизерів там.


0

Дякую @syockit за приголомшливу відповідь. проте мені довелося трохи переформатувати наступне, щоб змусити його працювати. Можливо, через проблеми сканування DOM:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width: sx,
        height: Math.round(img.height * sx / img.width)
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(process1, 0, this, 0);
}

//returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function (x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    }
}

process1 = function (self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
}
});

-1

Я просто провів сторінку порівняльних порівнянь, і якщо нещодавно щось не змінилося, я не міг побачити кращого зменшення розміру (масштабування) за допомогою полотна порівняно з простим css. Я протестував у FF6 Mac OSX 10.7. Ще трохи м'який порівняно з оригіналом.

Однак я натрапив на щось, що зробило величезне значення, і це було використання фільтрів зображень у браузерах, які підтримують полотно. Ви можете фактично маніпулювати зображеннями, як можна у Photoshop, розмиванням, різкістю, насиченістю, пульсацією, відтінками сірого тощо.

Потім я знайшов дивовижний плагін jQuery, який робить застосування цих фільтрів оснащеним: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Я просто застосую фільтр різкості після зміни розміру зображення, який повинен дати вам бажаний ефект. Мені навіть не довелося використовувати елемент полотна.


-1

Шукаєте ще одне чудове просте рішення?

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

Це рішення використовуватиме алгоритм зміни розміру браузера! :)


Питання в тому, щоб зменшити розмір зображення, а не просто змінити його розмір.
Jesús Carrera

[...] Я намагаюся змінити розмір jpg. Я спробував змінити розмір того ж jpg на пов'язаному веб-сайті та у фотошопі, і це чудово виглядає при зменшенні розміру. [...] чому ви не можете використовувати <img> Ісуса Карреру?
ale500
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.