web-dev-qa-db-fra.com

HTML5 Canvas Resize (Downscale) Image de haute qualité?

J'utilise des éléments de toile html5 pour redimensionner les images dans mon navigateur. Il s'avère que la qualité est très basse. J'ai trouvé ceci: Désactiver l'interpolation lors de la mise à l'échelle d'un <canvas> mais cela n'aide pas à augmenter la qualité.

Ci-dessous, mes codes css et js, ainsi que l'image numérisée avec Photoshop et mise à l'échelle dans l'API de canevas. 

Que dois-je faire pour obtenir une qualité optimale lors du redimensionnement d'une image dans le navigateur?

Remarque: je souhaite réduire une grande image à une petite, modifier la couleur d'un canevas et envoyer le résultat du canevas au serveur.

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

L'image redimensionnée avec photoshop:

enter image description here

L'image redimensionnée sur la toile:

enter image description here

Modifier: 

J'ai essayé de faire la réduction d'échelle en plusieurs étapes comme proposé dans:

Redimensionnement d'une image dans un canevas HTML5 and Canevas Html5 drawImage: comment appliquer l'antialiasing

C'est la fonction que j'ai utilisée: 

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


}

Voici le résultat si j'utilise un dimensionnement en 2 étapes: 

enter image description here

Voici le résultat si j'utilise un dimensionnement en 3 étapes: 

enter image description here

Voici le résultat si j'utilise un dimensionnement en 4 étapes: 

enter image description here

Voici le résultat si j'utilise un dimensionnement en 20 étapes: 

enter image description here

Remarque: il est apparu que l'amélioration de la qualité de l'image passait d'une étape à deux, mais plus vous ajoutez d'étapes au processus, plus l'image devient floue.

Y a-t-il un moyen de résoudre le problème selon lequel l'image devient plus floue au fur et à mesure que vous ajoutez des étapes? 

Edit 2013-10-04: J'ai essayé l'algorithme de GameAlchemist. Voici le résultat comparé à Photoshop.

PhotoShop Image: 

PhotoShop Image

Algorithme de GameAlchemist:

GameAlchemist's Algorithm

143
confile

Étant donné que votre problème est de réduire la taille de votre image, il est inutile de parler d'interpolation (qui consiste à créer des pixels). Le problème ici est le sous-échantillonnage. 

Pour sous-échantillonner une image, nous devons transformer chaque carré de p * p pixels de l'image d'origine en un seul pixel dans l'image de destination. 

Pour des raisons de performances, les navigateurs effectuent un sous-échantillonnage très simple: pour créer une image plus petite, ils sélectionnent UN pixel dans la source et utilisent sa valeur pour la destination. qui «oublie» certains détails et ajoute du bruit. 

Il existe toutefois une exception: le sous-échantillonnage de l'image 2X est très simple à calculer (4 pixels en moyenne pour en créer un) et est utilisé pour les pixels rétine/HiDPI, ce cas est traité correctement. Le navigateur utilise 4 pixels pour en faire un-. 

MAIS ... si vous utilisez plusieurs fois un sous-échantillonnage 2X, vous ferez face au problème selon lequel les erreurs d'arrondis successifs ajouteront trop de bruit.
Pire, vous ne redimensionnerez pas toujours par une puissance de deux, et redimensionner à la puissance la plus proche + un dernier redimensionnement est très bruyant.

Ce que vous recherchez, c’est un sous-échantillonnage au pixel près, c’est-à-dire: un ré-échantillonnage de l’image qui prendra en compte tous les pixels d’entrée, quelle que soit l’échelle.
Pour cela, nous devons calculer, pour chaque pixel en entrée, sa contribution à un, deux ou quatre pixels de destination, selon que la projection mise à l'échelle des pixels en entrée se trouve juste à l'intérieur d'un pixel de destination, chevauche une bordure X, une bordure Y , ou les deux.
(Un régime serait bien ici, mais je n'en ai pas.) 

Voici un exemple d’échelle de la toile par rapport à mon échelle de pixels parfaite au 1/3 d’un zombat.

Notez que la photo peut être redimensionnée dans votre navigateur et est .jpegized par S.O ..
Pourtant, nous constatons qu'il y a beaucoup moins de bruit, en particulier dans l'herbe derrière le wombat et les branches à sa droite. Le bruit dans la fourrure le rend plus contrasté, mais on dirait qu'il a des cheveux blancs - une image source différente -.
La bonne image est moins accrocheuse mais définitivement plus belle.

enter image description here

Voici le code pour faire la réduction de pixel parfaite:

résultat de violon: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
fiddle lui-même: 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's 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's 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's 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's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's 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;
}

C'est quite mémoire gourmande, puisqu'un tampon float est nécessaire pour stocker les valeurs intermédiaires de l'image de destination (-> si nous comptons la trame de résultat, nous utilisons 6 fois la mémoire de l'image source dans cet algorithme).
Il est également assez coûteux, car chaque pixel source est utilisé quelle que soit la taille de la destination et nous devons payer pour le getImageData/putImageDate, ce qui est assez lent également.
Mais il n’ya aucun moyen d’être plus rapide que de traiter chaque valeur source dans ce cas, et la situation n’est pas si grave: pour mon image de wombat 740 * 556, le traitement prend entre 30 et 40 ms.

160
GameAlchemist

Rééchantillonnage rapide de la toile avec une bonne qualité: http://jsfiddle.net/9g9Nv/442/

Mise à jour: version 2.0 (plus rapide, travailleurs Web + objets transférables) - 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);
}
45
ViliusL

Suggestion 1 - étendre le pipeline de processus

Vous pouvez utiliser le raccourci décrit dans les liens auxquels vous faites référence, mais vous semblez les utiliser de manière erronée.

Réduire n'est pas nécessaire pour redimensionner les images à des rapports supérieurs à 1: 2 (généralement, mais sans s'y limiter). C’est là que vous devez effectuer un drastique réduction d’échelle. Vous devez la scinder en deux étapes (et rarement plus) en fonction du contenu de l’image (en particulier les zones hautes fréquences telles que se produire).

Chaque fois que vous sous-échantillonnez une image, vous perdez des détails et des informations. Vous ne pouvez pas vous attendre à ce que l'image résultante soit aussi claire que l'original.

Si vous réduisez ensuite les images en plusieurs étapes, vous perdrez beaucoup d'informations et le résultat sera médiocre, comme vous l'avez déjà remarqué.

Essayez avec juste une étape supplémentaire ou au maximum deux.

Convolutions

Dans le cas de Photoshop, notez qu'il applique une convolution après que l'image a été ré-échantillonnée, telle que Accentuer. Comme l'interpolation bi-cubique n'a pas lieu uniquement, nous devons également ajouter les étapes suivies par Photoshop (avec la configuration par défaut) afin d'émuler totalement Photoshop.

Pour cet exemple, je vais utiliser la réponse originale à laquelle vous faites référence dans votre message, mais j’y ai ajouté une convolution plus nette afin d’améliorer la qualité du post-traitement (voir la démo en bas).

Voici le code pour ajouter un filtre de netteté (il est basé sur un filtre de convolution générique - je mets la matrice de pondération pour la netteté à l'intérieur ainsi qu'un facteur de mixage pour ajuster la prononciation de l'effet):

Utilisation:

sharpen(context, width, height, mixFactor);

La mixFactor est une valeur comprise entre [0.0, 1.0] et vous permet de minimiser l'effet d'accentuation - règle générale: plus la taille est petite, moins l'effet est nécessaire.

Fonction (basé sur cet extrait ):

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

Le résultat de l'utilisation de cette combinaison sera:

DEMO EN LIGNE ICI

Result downsample and sharpen convolution

En fonction du degré de netteté que vous souhaitez ajouter au mélange, vous pouvez obtenir le résultat de "flou" par défaut à très net:

Variations of sharpen

Suggestion 2 - implémentation d'algorithme de bas niveau

Si vous voulez obtenir le meilleur résultat possible sur le plan de la qualité, vous devrez passer au niveau inférieur et envisager d'implémenter, par exemple, ce nouvel algorithme.

Voir Sous-échantillonnage d'une image dépendante de l'interpolation (2011) de IEEE.
Voici un lien vers l’intégralité du document (PDF) .

Il n’existe actuellement aucune implémentation de cet algorithme en langage AFAIK en JavaScript, vous êtes donc prêt à tout pour pouvoir vous lancer dans cette tâche.

L'essence est (extraits du papier):

Abstrait

Un algorithme de sous-échantillonnage adaptatif orienté par interpolation est proposé pour le codage d’image à faible débit binaire dans cet article. Étant donné une image, le L’algorithme proposé permet d’obtenir une image basse résolution, à partir de qui une image de haute qualité avec la même résolution que l’entrée l'image peut être interpolée. Différent du traditionnel algorithmes de sous-échantillonnage, qui sont indépendants du processus d'interpolation, l'algorithme de sous-échantillonnage proposé articule le sous-échantillonnage au processus d'interpolation. Par conséquent, le L'algorithme de sous-échantillonnage proposé est capable de conserver l'original informations de l'image d'entrée dans la plus grande mesure. Le sous-échantillonné l'image est ensuite introduite en JPEG. Un poste basé sur une variation totale (TV) le traitement est ensuite appliqué à l'image basse résolution décompressée . Finalement, l'image traitée est interpolée pour maintenir le résolution d'origine de l'image d'entrée. Les résultats expérimentaux vérifient celle utilisant l'image sous-échantillonnée par l'algorithme proposé, un une image interpolée avec une qualité bien supérieure peut être obtenue. Outre, l'algorithme proposé est capable d'atteindre des performances supérieures à celles de JPEG pour le codage d’image à faible débit binaire.

Snapshot from paper

(voir le lien fourni pour tous les détails, formules, etc.)

28
user1693593

Si vous souhaitez utiliser uniquement le canevas, vous obtiendrez de meilleurs résultats en plusieurs étapes. Mais ce n'est pas encore assez bon. Pour une meilleure qualité, vous avez besoin d’une implémentation pure. Nous venons de publier le downscaler pica - haute vitesse à qualité/vitesse variable. En bref, il redimensionne 1280 * 1024px en ~ 0.1s et 5000 * 3000px en 1s, avec la plus haute qualité (filtre lanczos à 3 lobes). Pica a demo , où vous pouvez jouer avec vos images, vos niveaux de qualité et même l’essayer sur des appareils mobiles.

Pica n'a pas encore de masque flou, mais cela sera ajouté très bientôt. C'est beaucoup plus facile que de mettre en œuvre un filtre de convolution haute vitesse pour le redimensionnement.

18
Vitaly

Pourquoi utiliser le canevas pour redimensionner des images? Les navigateurs modernes utilisent tous une interpolation bicubique - le même processus utilisé par Photoshop (si vous le faites bien) - et ils le font plus rapidement que le processus de dessin. Spécifiez simplement la taille d'image souhaitée (utilisez une seule dimension, hauteur ou largeur, pour redimensionner proportionnellement).

Ceci est pris en charge par la plupart des navigateurs, y compris les versions ultérieures d’IE. Les versions précédentes peuvent nécessiter l'utilisation de CSS .

Une fonction simple (utilisant jQuery) pour redimensionner une image serait la suivante:

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

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

Ensuite, utilisez simplement la valeur renvoyée pour redimensionner l’image dans l’une ou les deux dimensions.

De toute évidence, vous pouvez apporter différentes améliorations, mais le travail est fait.

Collez le code suivant dans la console de cette page et observez ce qu'il advient des gravatars:

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";
});
17
Robusto

Pas la bonne réponse pour les personnes qui ont vraiment besoin de redimensionner l'image elle-même, mais juste pour réduire la taille du fichier.

J'ai eu un problème avec les images "directement à partir de l'appareil photo", que mes clients téléchargeaient souvent au format JPEG "non compressé". 

Ce n'est pas si connu, c'est que la toile permet (dans la plupart des navigateurs 2017) de changer la qualité du JPEG

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

Avec cette astuce, je pourrais réduire les images 4k x 3k avec> 10 Mo à 1 ou 2 Mo, bien sûr, cela dépend de vos besoins.

regardez ici

6
halfbit

Il s’agit du filtre de redimensionnement Hermite amélioré qui utilise 1 opérateur pour que la fenêtre ne gèle pas.

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

4
Calvintwr

Voici un service angulaire réutilisable pour le redimensionnement d’image/toile de haute qualité: https://Gist.github.com/fisch0920/37bac5e741eaec60e983

Le service prend en charge la convolution de Lanczos et la réduction d'échelle progressive. L'approche de convolution est de meilleure qualité au prix de ralentissement, tandis que l'approche de réduction d'échelle progressive produit des résultats raisonnablement anticrénelés et est nettement plus rapide.

Exemple d'utilisation:

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
    })
})
4
fisch2

J'ai trouvé une solution qui n'a pas besoin d'accéder directement aux données de pixels et de la parcourir pour effectuer le sous-échantillonnage. En fonction de la taille de l'image, cela peut nécessiter beaucoup de ressources et il serait préférable d'utiliser les algorithmes internes du navigateur.

La fonction drawImage () utilise une méthode de ré-échantillonnage par interpolation linéaire et plus proche voisin. Ce fonctionne bien lorsque vous ne redimensionnez pas plus de la moitié de la taille d'origine}.

Si vous ne redimensionnez que de moitié à la fois, les résultats seront plutôt bons et beaucoup plus rapides que l’accès aux données de pixels.

Cette fonction réduit l’échantillon de moitié à la fois jusqu’à atteindre la taille souhaitée:

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

Crédits à ce post

3
Jesús Carrera

Peut-être que vous pourrez essayer ceci, ce que j’utilise toujours dans mon projet. De cette manière, vous pouvez non seulement obtenir une image de haute qualité, mais aussi n’importe quel autre élément de votre toile.

/* 
 * @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
DeCoder

DEMO: Redimensionnement des images avec le fiddler JS et HTML Canvas Demo.

Vous pouvez trouver 3 méthodes différentes pour faire ce redimensionnement, ce qui vous aidera à comprendre comment fonctionne le code et pourquoi.

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

Le code complet de la démonstration et de la méthode TypeScript que vous souhaitez utiliser dans votre code se trouve dans le projet GitHub.

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

C'est le code final:

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
Eyal c

au lieu de . 85, si on ajoute 1.. Vous obtiendrez une réponse exacte.

data=canvas.toDataURL('image/jpeg', 1.0);

Vous pouvez obtenir une image claire et lumineuse. Vérifiez s'il vous plaît

0
Phoenix