Изменение размера изображения на холсте HTML5 - PullRequest
306 голосов
/ 20 февраля 2010

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

EDIT:

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

Canvas:

alt text

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

alt text

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

alt text

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

alt text

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

EDIT3:

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

Ответы [ 18 ]

388 голосов
/ 11 июля 2010

Так что вы будете делать, если все браузеры (на самом деле, 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 распространяется ли он на GPL2? Не знаю)

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

36 голосов
/ 19 августа 2013

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

enter image description here

Обновление : версия 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);
}
24 голосов
/ 24 сентября 2014

Попробуйте pica - это высокооптимизированный ресайзер с выбираемыми алгоритмами. Смотрите демо .

Например, исходное изображение из первого поста изменяется в 120 мс с фильтром Ланцоша и окном 3px или 60 мс с фильтром Box и окном 0.5px Для огромного 17-мегабайтного изображения, размер 5000x3000px, занимает ~ 1 с на рабочем столе и 3 с на мобильном.

Все принципы изменения размера были очень хорошо описаны в этой теме, и Пика не добавляет ракетостроения. Но он очень хорошо оптимизирован для современных JIT-ов и готов к использованию «из коробки» (через npm или bower). Кроме того, он использует веб-работников, когда они доступны, чтобы избежать зависаний интерфейса.

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

14 голосов
/ 14 октября 2010

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

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

Кроме того, извините за использование 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 голосов
/ 27 октября 2011

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

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

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

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

6 голосов
/ 13 июля 2010

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

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

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

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

  1. cx.mozImageSmoothingEnabled = false;
6 голосов
/ 13 февраля 2013

Это функция 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();

}
6 голосов
/ 22 февраля 2010

Если вы просто пытаетесь изменить размер изображения, я рекомендую установить 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 или путем рисования изображения в меньшем размере. 1046 *

См. этот вопрос для более подробной информации.

4 голосов
/ 23 марта 2011

Для изменения размера изображения с шириной меньше оригинала я использую:

    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 голосов
/ 10 марта 2010

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

alt text

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';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...