Изменение размера изображения на холсте HTML5


314

Я пытаюсь создать уменьшенное изображение на стороне клиента, используя javascript и элемент canvas, но когда я уменьшаю изображение, оно выглядит ужасно. Похоже, что он был уменьшен в фотошопе с передискретизацией, установленной на «Nearest Neighbor» вместо Bicubic. Я знаю, что можно сделать так, чтобы это выглядело правильно, потому что этот сайт может делать это просто отлично, используя холст. Я пытался использовать тот же код, который они делают, как показано в ссылке «[Source]», но он все равно выглядит ужасно. Есть что-то, что я пропускаю, какие-то настройки, которые нужно установить или что-то?

РЕДАКТИРОВАТЬ:

Я пытаюсь изменить размер 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:

альтернативный текст

Холст:

альтернативный текст

Изображение с рендерингом изображения: optimizeQuality установлено и масштабировано по ширине / высоте:

альтернативный текст

Изображение с рендерингом изображения: optimizeQuality установлено и масштабировано с -moz-transform:

альтернативный текст

Размер холста по пикселю:

альтернативный текст

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

EDIT3:

Исходное изображение


Можете ли вы опубликовать код, который вы используете для изменения размера изображения?
Хави

Вы пытаетесь изменить размер изображения GIF или аналогичного изображения с ограниченной палитрой? Даже в фотошопе эти изображения не будут хорошо масштабироваться, если вы не конвертируете их в RGB.
leepowers

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

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

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

Ответы:


393

Так что же делать, если все браузеры (на самом деле, Chrome 5 дал мне довольно хороший) не дадут вам достаточно хорошего качества ресэмплирования? Вы сами реализуете их! Да ладно, мы вступаем в новую эру Web 3.0, HTML5-совместимых браузеров, супероптимизированных JIT-компиляторов 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 Gallery Generator , распространяется ли он также под GPL2? Не знаю)

† На самом деле из-за ограничения JavaScript, многоядерный не поддерживается.


2
На самом деле я пытался реализовать это сам, делая то же самое, что и вы, копируя код из редактора изображений с открытым исходным кодом. Так как я не смог найти надежную документацию по алгоритму, мне было трудно его оптимизировать. В конце концов, мой был немного медленным (потребовалось несколько секунд, чтобы изменить размер изображения). Когда я получу шанс, я попробую ваш и посмотрим, будет ли это быстрее. И я думаю, что теперь веб-разработчики делают возможным многоядерный JavaScript. Я собирался попробовать использовать их, чтобы ускорить его, но мне было трудно понять, как превратить это в многопоточный алгоритм
Telanor

3
Извини, забыл это! Я отредактировал ответ. В любом случае это не будет быстрым, бикубика должна быть быстрее. Не говоря уже о алгоритме, который я использовал, это не обычное двухстороннее изменение размера (которое построчно, горизонтальное, а затем вертикальное), поэтому оно работает медленнее.
syockit

5
Вы потрясающий и заслуживаете тонны удивительного.
Роклан

5
Это дает приличные результаты, но требует 7,4 секунды для изображения 1,8 Мп в последней версии Chrome ...
mpen

2
Как такие методы справляются с такой высокой оценкой ?? Показанное решение полностью не учитывает логарифмическую шкалу, используемую для хранения информации о цвете. RGB 127 127 127 - это одна четверть яркости 255, 255, 255, а не половина. Отбор проб в растворе приводит к затемнению изображения. Жаль, что это закрыто, так как существует очень простой и быстрый способ уменьшить размер, который дает даже лучшие результаты, чем в фотошопе (у OP должны быть неправильные настройки), приведенный пример
Blindman67

37

Алгоритм быстрого изменения размера изображения / повторной выборки с использованием фильтра Эрмита с JavaScript. Поддержка прозрачности, дает хорошее качество. Предварительный просмотр:

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

Обновить : версия 2.0 добавлена ​​на GitHub (быстрее, веб-работники + переносимые объекты). Наконец я получил это работает!

Git: https://github.com/viliusle/Hermite-resize
Демо: 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 мс с фильтром Lanczos и окном 3px или 60 мс с фильтром Box и окном 0.5px. Для огромного 17-мегабайтного изображения размер 5000x3000px занимает ~ 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-холста, которые могут быть полезны здесь:

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

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

Я не добавлял материал Lanczos выше, поэтому не стесняйтесь добавлять его в качестве сравнения, если хотите.


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

Я настоятельно рекомендую вам проверить эту ссылку и убедиться, что она установлена ​​в true.

Управление масштабированием изображения

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

Gecko 1.9.2 представил свойство mozImageSmoothingEnabled для элемента canvas; если это логическое значение равно false, изображения не будут сглаживаться при масштабировании. Это свойство имеет значение true по умолчанию. просмотреть открытку?

  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

я получил это изображение, щелкнув правой кнопкой мыши элемент canvas в Firefox и сохранив как.

альтернативный текст

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

Итак, кое-что интересное, что я нашел некоторое время назад во время работы с canvas, которое может быть полезным:

Чтобы изменить размер элемента управления canvas самостоятельно, вам нужно использовать атрибуты height=""and width=""(или canvas.width/canvas.height элемента elements). Если вы используете CSS для изменения размера холста, он будет фактически растягивать (то есть: изменять размер) содержимое холста, чтобы соответствовать всему холсту (а не просто увеличивать или уменьшать площадь холста).

Стоит попытаться нарисовать изображение в элементе управления canvas с атрибутами высоты и ширины, равными размеру изображения, а затем с помощью CSS изменить размер холста до нужного размера. Возможно, это будет использовать другой алгоритм изменения размера.

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

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

Удачи!


1
Установка ширины или высоты холста с помощью атрибутов HTML приводит к тому, что холст очищается, поэтому с помощью этого метода невозможно изменить размер. Также SVG предназначен для работы с математическими изображениями. Мне нужно уметь рисовать PNG и тому подобное, так что это мне не поможет.
Теланор

Я обнаружил, что установка высоты и ширины холста и изменение размера с помощью CSS не помогают (в Chrome). Даже выполнение изменения размера с использованием -webkit-transform вместо CSS width / height не приводит к интерполяции.
Нестор

2

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

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.
})

история

Это действительно после многих раундов исследований, чтения и попыток.

Алгоритм изменения размера использует скрипт Hermite @ ViliusL (изменение размера Hermite действительно является самым быстрым и дает достаточно хороший результат). Расширенный с функциями, которые вам нужны.

Forks 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

Я только что провел страницу параллельных сравнений, и, если что-то не изменилось в последнее время, я не вижу лучшего сокращения (масштабирования) с использованием canvas и простого CSS. Я тестировал в FF6 Mac OSX 10.7. Все еще немного мягкий по сравнению с оригиналом.

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

Затем я нашел удивительный плагин jQuery, который делает применение этих фильтров простым: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

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


-1

Ищете другое отличное простое решение?

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

Это решение будет использовать алгоритм изменения размера браузера! :)


Вопрос в том, чтобы уменьшить изображение, а не просто изменить его размер.
Хесус Каррера

[...] Я пытаюсь изменить размер JPG. Я попытался изменить размер того же jpg на связанном сайте и в фотошопе, и он выглядит хорошо, когда его уменьшают. [...] почему вы не можете использовать <img> Jesus Carrera?
ale500
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.