Как масштабировать изображения на холсте html5 с лучшей интерполяцией?

Прежде всего: что я пытаюсь сделать?

У меня есть приложение для просмотра изображений. Он использует элемент canvas для визуализации изображения. Вы можете увеличить, вы можете уменьшить масштаб, и вы можете перетащить его. Эта часть отлично работает прямо сейчас.

Но позвольте мне сказать, что у меня есть изображение с большим количеством текста. У него разрешение 1200х1700, а у моего холста 1200х900. Первоначально при уменьшении это приводит к разрешению ~ 560x800.

Мой фактический рисунок выглядит так:

drawImage(src, srcOffsetX, srcOffsetY, sourceViewWidth, sourceViewHeight,
destOffsetX, destOffsetY, destWidth, destHeight);

Небольшой текст на этом изображении выглядит очень, очень плохо, особенно по сравнению с другими программами просмотра изображений (например, IrfanView) или даже элементом html <img>.

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

Ну, я искал в каждом углу Interwebs в течение 4-5 часов подряд и не нашел то, что мне нужно. Я нашел вариант "imageSmoothingEnabled", "образ-рендеринг" стили CSS, которые вы не можете использовать на холсте, рендеринг в поплавковой позиции и многих реализациях JavaScript алгоритмов интерполяции (те далеки, чтобы замедлить для моей цели).

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

Итак: есть ли хороший и быстрый способ улучшить интерполяцию? Моя текущая идея состоит в том, чтобы создать объект изображения, изменить его размер (потому что img имеет хорошую интерполяцию при масштабировании!) И затем отобразить его. К сожалению, применение img.width влияет только на отображаемую ширину...

Обновление: благодаря Саймону я смог решить мою проблему. Вот алгоритм динамического масштабирования, который я использовал. Обратите внимание, что он сохраняет соотношение сторон, параметр height предназначен только для избежания дополнительных вычислений с плавающей запятой. Это только уменьшится прямо сейчас.

scale(destWidth, destHeight){
        var start = new Date().getTime();
        var scalingSteps = 0;
        var ctx = this._sourceImageCanvasContext;
        var curWidth = this._sourceImageWidth;
        var curHeight = this._sourceImageHeight;

        var lastWidth = this._sourceImageWidth;
        var lastHeight = this._sourceImageHeight;

        var end = false;
        var scale=0.75;
        while(end==false){
            scalingSteps +=1;
            curWidth *= scale;
            curHeight *= scale;
            if(curWidth < destWidth){
                curWidth = destWidth;
                curHeight = destHeight;
                end=true;
            }
            ctx.drawImage(this._sourceImageCanvas, 0, 0, Math.round(lastWidth), Math.round(lastHeight), 0, 0, Math.round(curWidth), Math.round(curHeight));
            lastWidth = curWidth;
            lastHeight = curHeight;
        }
        var endTime =new Date().getTime();
        console.log("execution time: "+ ( endTime - start) + "ms. scale per frame: "+scale+ " scaling step count: "+scalingSteps);
    }

Ответ 1

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

Рассмотрим изображение, которое вы хотите нарисовать в масштабе 1/6. Вы можете сделать это:

var w = 1280;
var h = 853;

ctx.drawImage(img, 0, 0, w/6, h/6);   

Или вы можете нарисовать его на холсте в памяти в масштабе 1/2, затем 1/2 масштаба снова, затем 1/2 масштаба снова. Результатом является изображение в масштабе 1/6, но мы используем три шага:

var can2 = document.createElement('canvas');
can2.width = w/2;
can2.height = w/2;
var ctx2 = can2.getContext('2d');

ctx2.drawImage(img, 0, 0, w/2, h/2);
ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);

Затем вы можете вернуть это в исходный контекст:

ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);

Вы можете видеть разницу в реальном времени, здесь:

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');

var img = new Image();
var w = 1280;
var h = 853;
img.onload = function() {
    // step it down only once to 1/6 size:
    ctx.drawImage(img, 0, 0, w/6, h/6);   
    
    // Step it down several times
    var can2 = document.createElement('canvas');
    can2.width = w/2;
    can2.height = w/2;
    var ctx2 = can2.getContext('2d');
    
    // Draw it at 1/2 size 3 times (step down three times)
    
    ctx2.drawImage(img, 0, 0, w/2, h/2);
    ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
    ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);
    ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);
}



img.src = 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Equus_quagga_%28Namutoni%2C_2012%29.jpg/1280px-Equus_quagga_%28Namutoni%2C_2012%29.jpg'
canvas {
    border: 1px solid gray;
}
<canvas id="canvas1" width="400" height="400"></canvas>