Html5 canvas drawImage: как применить сглаживание
пожалуйста, посмотрите на следующий пример:
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 применяет сглаживание автоматически. Я пробовал много разных способов, но это, кажется, не работает. Не могли бы вы рассказать мне, как я могу получить сглаженное изображение?
Спасибо.
6 ответов:
причина
некоторые изображения просто очень трудно вниз-образец и интерполировать например, этот с кривыми, когда вы хотите перейти от большого размера к маленькому.
браузеры, как правило, используют билинейную (выборка 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>поддержка фильтра как ogf Oct / 2018:
CanvasRenderingContext2D.filter api.CanvasRenderingContext2D.filter On Standard Track, Experimental https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/filter DESKTOP > |Chrome |Edge |Firefox |IE |Opera |Safari :----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:-------- filter ! | 52 | ? | 49 | - | - | - MOBILE > |Chrome/A |Edge/mob |Firefox/A |Opera/A |Safari/iOS|Webview/A :----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:-------- filter ! | 52 | ? | 49 | - | - | 52 ! = Experimental Data from MDN - "npm i -g mdncomp" (c) epistemexобновление 2017: Теперь есть новостройки определены в спецификациях, для настройки качества интерполяции:
context.imageSmoothingQuality = "low|medium|high"в настоящее время он поддерживается только в Chrome. Фактические методы, используемые на уровне, оставлены на усмотрение поставщика, но разумно предположить, что Lanczos для "высокого" или чего-то эквивалентного по качеству. Это означает, что шаг вниз может быть пропущен полностью, или большие шаги могут быть использованы с меньшим количеством перерисовок, в зависимости от размера изображения и
поддержка
imageSmoothingQuality:CanvasRenderingContext2D.imageSmoothingQuality api.CanvasRenderingContext2D.imageSmoothingQuality On Standard Track, Experimental https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality DESKTOP > |Chrome |Edge |Firefox |IE |Opera |Safari :----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------: imageSmoothingQuality !| 54 | ? | - | ? | 41 | Y MOBILE > |Chrome/A |Edge/mob |Firefox/A |Opera/A |Safari/iOS|Webview/A :----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------: imageSmoothingQuality !| 54 | ? | - | 41 | Y | 54 ! = Experimental Data from MDN - "npm i -g mdncomp" (c) epistemexбраузер. До тех пор..:
конец передачирешение заключается в использовании шаг-вниз чтобы получить правильный результат. Шаг вниз означает, что вы уменьшаете размер шагами, чтобы позволить ограниченному диапазону интерполяции охватить достаточное количество пикселей для выборки.
это позволит получить хорошие результаты также с билинейной интерполяцией (на самом деле он ведет себя так же, как Бик-кубический при этом) и накладные расходы минимальны, так как на каждом шаге меньше пикселей для выборки.
идеальный шаг-перейти к половина резолюции в каждом шаге, пока вы не установите целевой размер (спасибо Джо Мейбл за упоминание об этом!).
использование прямого масштабирования, как в исходном вопросе:
через понижающий как показано ниже:
в этом случае вам нужно будет спуститься в 3 шага:
в шаге 1 мы уменьшаем изображение до половины с помощью холста вне экрана:
// step 1 - create off-screen canvas var oc = document.createElement('canvas'), octx = oc.getContext('2d'); oc.width = img.width * 0.5; oc.height = img.height * 0.5; octx.drawImage(img, 0, 0, oc.width, oc.height);Шаг 2 повторно использует холст вне экрана и рисует изображение, уменьшенное до половины снова:
// step 2 octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);и мы еще раз рисуем на главном холсте, опять уменьшаем
половинуно до финала размер:// step 3 ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5, 0, 0, canvas.width, canvas.height);Совет:
вы можете рассчитать общее количество необходимых шагов, используя эту формулу (она включает в себя последний шаг для установки целевого размера):
steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))
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,/, ""); }
в дополнение к ответу Кена, здесь еще одно решение для выполнения понижающей дискретизации пополам (так что результат выглядит хорошо с помощью алгоритма браузера):
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] );кредиты этот пост
Я создал многоразовый угловой сервис для обработки высококачественного изменения размеров изображений для всех, кто заинтересован: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 }) })
если кто-то еще ищет ответ, есть еще один способ, которым вы можете использовать фоновое изображение вместо 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";


Comments