HTML5 Canvas Resize (Downscale) Изображение высокого качества?

150

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

Ниже приведен код css и js, а также изображение, замаскированное в Photoshop и масштабированное в API холста.

Что мне нужно сделать, чтобы получить оптимальное качество при масштабировании изображения в браузере?

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

CSS:

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

Изображение изменено размером с фотошопом:

Изображение 374

Изображение изменено на холсте:

Изображение 375

Изменить:

Я попытался сделать масштабирование более чем на один шаг, как было предложено в:

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

Это функция, которую я использовал:

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

Вот результат, если я использую двухэтапный размер:

Изображение 376

Вот результат, если я использую 3-х ступенчатый размер:

Изображение 377

Вот результат, если я использую 4-х ступенчатый размер:

Изображение 378

Вот результат, если я использую 20-ти ступенчатый размер:

Изображение 379

Примечание. Оказывается, что с 1 до 2 шагов происходит значительное улучшение качества изображения, но чем больше шагов вы добавляете к процессу, тем более нечетким становится изображение.

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

Edit 2013-10-04: Я пробовал алгоритм GameAlchemist. Вот результат по сравнению с Photoshop.

Изображение PhotoShop:

Изображение 380

Алгоритм GameAlchemist:

Изображение 381

  • 2
    Вы можете попробовать постепенно масштабировать изображение: stackoverflow.com/questions/18761404/…
  • 1
    Возможный дубликат Html5 canvas drawImage: как применять сглаживание . Посмотрите, не работает ли это. Если изображения большие и уменьшены до малого размера, вам нужно будет сделать это поэтапно (см. Пример изображения в ссылке)
Показать ещё 24 комментария
Теги:
canvas
html5-canvas

12 ответов

160
Лучший ответ

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

Чтобы уменьшить изображение, нам нужно превратить каждый квадрат p * p пикселей в исходное изображение в один пиксель в целевом изображении.

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

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

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

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

Для этого мы должны вычислить для каждого входного пикселя его вклад в один, два или четыре пикселя назначения, в зависимости от того, масштабированная проекция входных пикселей находится прямо внутри целевых пикселей, перекрывает границу X, границу Y или оба.
(Схема была бы приятной здесь, но у меня ее нет.)

Вот пример масштаба холста и моего идеального масштаба пикселя в масштабе 1/3 зомба.

Обратите внимание на то, что изображение может быть масштабировано в вашем браузере, и оно имеет .jpegized by S.O..
Тем не менее мы видим, что гораздо меньше шума, особенно в траве за вомбатом, а ветки справа. Шум в мехах делает его более контрастным, но похоже, что он получил белые волосы - не похоже на исходное изображение.

Правильное изображение менее запоминающееся, но окончательно более приятное.

Изображение 5046

Здесь код, чтобы сделать пиксельное идеальное масштабирование:

Результат скрипта: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
fiddle: http://jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 |  tx;    // rounded : target pixel x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

Это довольно жадная память, поскольку для хранения промежуточных значений целевого изображения требуется буфер с плавающей запятой (- > если мы подсчитываем холст результата, мы используем в 6 раз память исходного изображения в этом алгоритме). Это также довольно дорого, так как каждый пиксель источника используется независимо от размера адресата, и мы также должны платить за getImageData/putImageDate. Но нет способа быть быстрее, чем обрабатывать каждое значение источника в этом случае, и ситуация не так уж плоха: для моего 740 * 556 изображений вомбата обработка занимает от 30 до 40 мс.

  • 0
    Может ли быть быстрее, если вы масштабируете изображение, прежде чем поместить его на холст?
  • 0
    я не понимаю ... кажется, это то, что я делаю. Буфер, а также созданный мной холст (resCV) имеют размер масштабированного изображения. Я думаю, что единственный способ сделать это быстрее - использовать целочисленные вычисления, подобные брешенам. Но 40 мс медленнее только для видеоигры (25 к / с), а не для приложения для рисования.
Показать ещё 30 комментариев
43

Быстрое перетаскивание холста с хорошим качеством: http://jsfiddle.net/9g9Nv/442/

Обновление: версия 2.0 (быстрее, веб-рабочие + переносимые объекты) - https://github.com/viliusle/Hermite-resize

/**
 * 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);
}
  • 0
    Мне нужно лучшее качество
  • 15
    исправлено, я изменил "хорошо" на "лучше", теперь это нормально? : D. С другой стороны, если вы хотите наилучшую возможную повторную выборку - используйте imagemagick.
Показать ещё 14 комментариев
29

Предложение 1 - расширение технологической линии

Вы можете использовать понижающее действие, как я описываю в ссылках, на которые вы ссылаетесь, но вы, по-видимому, используете их неправильно.

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

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

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

Попробуйте выполнить только один дополнительный шаг или на вершинах два.

сверток

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

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

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

Применение:

sharpen(context, width, height, mixFactor);

mixFactor - это значение между [0.0, 1.0] и позволяет преуменьшать эффект резкости - правило большого пальца: чем меньше размер, тем меньше эффект необходим.

Функция (на основе этот фрагмент):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;

    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

Результатом использования этой комбинации будет:

ОНЛАЙН ДЕМО ЗДЕСЬ

Изображение 5047

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

Изображение 5048

Предложение 2 - реализация алгоритма низкого уровня

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

См. Интерполяция-зависимая обратная выборка изображений (2011) от IEEE.
Вот ссылка на бумагу в полном объеме (PDF).

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

Сущность (выдержки из статьи):

Абстрактный

Предлагается адаптивный алгоритм адаптивной вниз выборки для интерполяции для кодирования изображений с низкой скоростью передачи данных в этой статье. Учитывая изображение, предлагаемый алгоритм способен получить изображение с низким разрешением, от который имеет высокое качество изображения с тем же разрешением, что и вход изображение может быть интерполировано. В отличие от традиционных алгоритмы с низкой дискретизацией, которые не зависят от интерполяционный процесс, предлагаемый алгоритм понижающей выборки шарнирно закрепляет вниз-выборки в процесс интерполяции. Следовательно, предложенный алгоритм сэмплирования выборки способен поддерживать исходный информацию о входном изображении в наибольшей степени. Пронумерованный выбор изображение затем подается в JPEG. Общая вариация (ТВ) обработка затем применяется к декомпрессированному изображению с низким разрешением. В конечном итоге обработанное изображение интерполируется для поддержания оригинальное разрешение входного изображения. Экспериментальные результаты подтверждают что использование уменьшенного дискретизированного изображения по предлагаемому алгоритму, может быть достигнуто интерполированное изображение с гораздо более высоким качеством. Кроме, предлагаемый алгоритм способен достичь превосходной производительности, чем JPEG для кодирования изображения с низкой скоростью передачи.

Изображение 5049

(см. приведенную ссылку для всех деталей, формул и т.д.)

  • 0
    Это тоже отличное решение. Спасибо!
  • 0
    Это отличное решение. Я пробовал это на png файлах с прозрачными областями. Вот результат: jsfiddle.net/confile/5CD4N Вы знаете, что нужно сделать, чтобы это работало?
Показать ещё 3 комментария
18

Если вы хотите использовать только холст, лучший результат будет с несколькими неудачами. Но это еще не очень хорошо. Для лучшего качества вам нужна чистая реализация js. Мы только что выпустили pica - высокоскоростное понижающее сканирование с переменным качеством/скоростью. Короче говоря, он изменяет размеры 1280 * 1024 пикселей в ~ 0,1 с и изображение 5000 * 3000 пикселей в 1 с, с самым высоким качеством (фильтр lanczos с 3 лепестками). Pica имеет demo, где вы можете играть с вашими изображениями, уровнями качества и даже попробовать на мобильных устройствах.

У Pica еще нет нерезкой маски, но это будет добавлено очень скоро. Это намного проще, чем использовать высокоскоростной фильтр свертки для изменения размера.

17

Зачем использовать холст для изменения размера изображений? Все современные браузеры используют бикубическую интерполяцию - тот же процесс, который используется в Photoshop (если вы все делаете правильно), - и они делают это быстрее, чем процесс canvas. Просто укажите нужный размер изображения (используйте только одно измерение, высоту или ширину, чтобы пропорционально изменить размер).

Это поддерживается большинством браузеров, включая более поздние версии IE. Более ранние версии могут требовать специфичного для браузера CSS.

Простая функция (использующая jQuery) для изменения размера изображения будет выглядеть так:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

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

Очевидно, что вы можете внести некоторые улучшения, но это сделает работу.

Вставьте следующий код в консоль этой страницы и посмотрите, что происходит с граватарами:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});
  • 0
    Пожалуйста, отправьте пример кода о том, как это сделать.
  • 2
    Также обратите внимание, что если вы укажете только одно измерение, (современный) браузер автоматически сохранит естественное соотношение сторон изображения.
Показать ещё 8 комментариев
6

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

У меня была проблема с изображениями "прямо с камеры", которые мои клиенты часто загружали в "несжатом" формате JPEG.

Не так хорошо известно, что Canvas поддерживает (в большинстве браузеров 2017) изменение качества JPEG

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

С помощью этого трюка я мог бы уменьшить изображение размером 4K x 3K с> 10 МБ до 1 или 2 МБ, конечно, это зависит от ваших потребностей.

Смотри сюда

4

Это улучшенный фильтр размера Эрмита, в котором используется 1 рабочий, чтобы окно не зависало.

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

4

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

Служба поддерживает свертку 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
    })
})
3

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

Функция 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] );

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

  • 0
    Не могли бы вы опубликовать jsfiddle и некоторые изображения в результате?
  • 0
    В ссылке внизу вы можете найти полученные изображения, используя эту технику
0

ДЕМО: изменение размера изображений с помощью JS и HTML Canvas Demo fiddler.

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

https://jsfiddle.net/1b68eLdr/93089/

Полный код демонстрационного и метода TypeScript, который вы можете использовать в своем коде, можно найти в проекте GitHub.

https://github.com/eyalc4/ts-image-resizer

Это окончательный код:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the size by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}
0

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

/* 
 * @parame canvas => canvas object
 * @parame rate => the pixel quality
 */
function setCanvasSize(canvas, rate) {
    const scaleRate = rate;
    canvas.width = window.innerWidth * scaleRate;
    canvas.height = window.innerHeight * scaleRate;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    canvas.getContext('2d').scale(scaleRate, scaleRate);
}
0

context.scale(xScale, yScale)

<canvas id="c"></canvas>
<hr/>
<img id="i" />

<script>
var i = document.getElementById('i');

i.onload = function(){
    var width = this.naturalWidth,
        height = this.naturalHeight,
        canvas = document.getElementById('c'),
        ctx = canvas.getContext('2d');

    canvas.width = Math.floor(width / 2);
    canvas.height = Math.floor(height / 2);

    ctx.scale(0.5, 0.5);
    ctx.drawImage(this, 0, 0);
    ctx.rect(0,0,500,500);
    ctx.stroke();

    // restore original 1x1 scale
    ctx.scale(2, 2);
    ctx.rect(0,0,500,500);
    ctx.stroke();
};

i.src = 'https://static.md/b70a511140758c63f07b618da5137b5d.png';
</script>

Ещё вопросы

Сообщество Overcoder
Наверх
Меню