Html5 холст drawImage: как применять сглаживание

Пожалуйста, посмотрите на следующий пример:

http://jsfiddle.net/MLGr4/47/

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

img = new Image();
img.onload = function(){
    canvas.width = 400;
    canvas.height = 150;
    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 400, 150);
}
img.src = "http://openwalls.com/image/1734/colored_lines_on_blue_background_1920x1200.jpg";

Как видите, изображение не имеет сглаживания, хотя говорят, что drawImage автоматически применяет сглаживание. Я пробовал много разных способов, но это не похоже на работу. Не могли бы вы сказать мне, как я могу получить сглаженное изображение? Благодарю.

Ответ 1

причина

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

Браузеры, как правило, используют билинейную (выборка 2x2) интерполяцию с элементом canvas, а не бикубическую (выборку 4x4) по (вероятным) причинам производительности.

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

С точки зрения сигнала /DSP это можно увидеть как слишком высокое пороговое значение фильтра нижних частот, которое может привести к наложению алиасов, если в сигнале много высоких частот (деталей).

Решение

Обновление 2018:

Здесь полезный трюк, который вы можете использовать для браузеров, которые поддерживают свойство filter в 2D-контексте. Это предварительно размывает изображение, которое по сути аналогично повторной выборке, а затем уменьшает масштаб. Это учитывает большие шаги, но только требует двух шагов и двух ничьих.

Предварительно размыть, используя количество шагов (исходный размер/размер места назначения /2) в качестве радиуса (вам может понадобиться отрегулировать это эвристически на основе браузера и нечетных/четных шагов - здесь только показано упрощенно):

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

if (typeof ctx.filter === "undefined") {
 alert("Sorry, the browser doesn't support Context2D filters.")
}

const img = new Image;
img.onload = function() {

  // step 1
  const oc = document.createElement('canvas');
  const octx = oc.getContext('2d');
  oc.width = this.width;
  oc.height = this.height;

  // steo 2: pre-filter image using steps as radius
  const steps = (oc.width / canvas.width)>>1;
  octx.filter = 'blur(${steps}px)';
  octx.drawImage(this, 0, 0);

  // step 3, draw scaled
  ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);

}
img.src = "//i.stack.imgur.com/cYfuM.jpg";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<br/><p>Original was 1600x1200, reduced to 400x300 canvas</p><br/>
<canvas id="canvas" width=400 height=250></canvas>

Ответ 2

Я очень рекомендую pica для таких задач. Его качество превосходит несколько сокращений и одновременно является довольно быстрым. Вот demo.

Ответ 3

    var getBase64Image = function(img, quality) {
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");

    //----- origin draw ---
    ctx.drawImage(img, 0, 0, img.width, img.height);

    //------ reduced draw ---
    var canvas2 = document.createElement("canvas");
    canvas2.width = img.width * quality;
    canvas2.height = img.height * quality;
    var ctx2 = canvas2.getContext("2d");
    ctx2.drawImage(canvas, 0, 0, img.width * quality, img.height * quality);

    // -- back from reduced draw ---
    ctx.drawImage(canvas2, 0, 0, img.width, img.height);

    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
    // return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}

Ответ 4

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

  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] );

Кредиты этот пост

Ответ 5

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

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

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

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

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it 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
    })
})

Ответ 6

Если кто-то еще ищет ответ, есть еще один способ, которым вы можете использовать фоновое изображение вместо drawImage(). Таким образом, вы не потеряете качество изображения.

JS:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
   var url = "http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

    img=new Image();
    img.onload=function(){

        canvas.style.backgroundImage = "url(\'" + url + "\')"

    }
    img.src="http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

рабочая демо