Изменение размера изображения на холсте 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 ]

3 голосов
/ 27 августа 2014

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

Функция 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 голосов
/ 06 июля 2010

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

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

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

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

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

Удачи!

2 голосов
/ 31 июля 2014

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

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

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

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

ОБНОВЛЕНИЕ ОКТЯБРЯ 2018 : В наши дни мой пример более академичен, чем все остальное. Webgl почти на 100%, поэтому вам лучше изменить его размер, чтобы получить схожие результаты, но быстрее. Я считаю, что PICA.js делает это. -

1 голос
/ 10 декабря 2015

Быстрое и простое изменение размера изображения Javascript:

https://github.com/calvintwr/Hermite-resize

Использование:

h.resize({
    source: document.getElementById('image'), // any canvas or image elements, jQuery or native
    width: 400,
    height: 600,
    output: 'image', // [optional] `image` or `canvas`. If not entered output is same as input element.
    quality: 0.7, // [optional] applicable for `image` output only
}, function(output) {
    //your callback
});

История

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

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

Работает с 1 рабочим, чтобы изменить размер, чтобы он не зависал при изменении размера вашего браузера, в отличие от всех других JS-преобразователей.

1 голос
/ 17 сентября 2014

Я преобразовал ответ @ 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
    })
})
0 голосов
/ 20 августа 2014

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

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

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

0 голосов
/ 14 августа 2013

Спасибо @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";
}
});
0 голосов
/ 24 августа 2011

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

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

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

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

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