180 votes

HTML5 Canvas Redimensionnement (réduction d'échelle) d'une image de haute qualité ?

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

Vous trouverez ci-dessous mon code css et js ainsi que l'image mise à l'échelle avec Photoshop et mise à l'échelle dans l'API canvas.

Que dois-je faire pour obtenir une qualité optimale lors de la mise à l'échelle d'une image dans le navigateur ?

Note : Je veux réduire une grande image à une petite, modifier la couleur dans 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 a été redimensionnée avec photoshop :

enter image description here

L'image redimensionnée sur la toile :

enter image description here

Edit :

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

Redimensionnement d'une image dans un canevas HTML5 y Html5 canvas drawImage : comment appliquer l'anticrénelage ?

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 une taille inférieure de 2 pas :

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 une taille inférieure de 20 :

enter image description here

Remarque : il s'avère que de 1 à 2 étapes, la qualité de l'image s'améliore considérablement, mais plus vous ajoutez d'étapes au processus, plus l'image devient floue.

Existe-t-il un moyen de résoudre le problème de l'image qui devient plus floue au fur et à mesure que l'on ajoute des étapes ?

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

Image PhotoShop :

PhotoShop Image

Algorithme de l'alchimiste du jeu :

GameAlchemist's Algorithm

2 votes

Vous pouvez essayer de mettre votre image à l'échelle de manière incrémentielle : stackoverflow.com/questions/18761404/

1 votes

Duplicata possible de Html5 canvas drawImage : comment appliquer l'anticrénelage ? . Voir si cela ne fonctionne pas. Si les images sont grandes et réduites à une petite taille, vous devrez le faire par étapes (voir les exemples d'images dans le lien).

0 votes

Vous avez réglé le rendu d'image sur "optimizeSpeed". On dirait que ça va donner un redimensionnement plus moche. Avez-vous essayé de le régler sur "optimizeQuality" ?

194voto

GameAlchemist Points 7905

Puisque votre problème est de réduire l'échelle 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 originale en un seul pixel dans l'image de destination.

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

Il y a cependant une exception à cette règle : étant donné que le sous-échantillonnage de l'image 2X est très simple à calculer (moyenne de 4 pixels pour en faire un) et qu'il est utilisé pour les pixels rétina/HiDPI, ce cas est traité correctement - le navigateur utilise bien 4 pixels pour en faire un.

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

Ce que vous recherchez est un sous-échantillonnage parfait, 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 ce faire, nous devons calculer, pour chaque pixel d'entrée, sa contribution à un, deux ou quatre pixels de destination selon que la projection à l'échelle des pixels d'entrée se trouve à l'intérieur d'un pixel de destination, qu'elle chevauche un bord X, un bord Y ou les deux.
( Un schéma serait bien ici, mais je n'en ai pas. )

Voici un exemple de l'échelle de la toile par rapport à mon échelle parfaite de pixel sur une échelle 1/3 d'un zombat.

Notez que l'image peut être mise à l'échelle dans votre navigateur, et qu'elle est jpegisée par S.O..
Pourtant, nous voyons qu'il y a beaucoup moins de bruit, notamment dans l'herbe derrière le wombat, et dans les branches à sa droite. Le bruit dans la fourrure la rend plus contrastée, mais on dirait qu'il a des poils blancs - contrairement à l'image source -.
L'image de droite est moins accrocheuse mais définitivement plus belle.

enter image description here

Voici le code pour faire la réduction d'échelle parfaite au pixel près :

résultat du violon : http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
le violon 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;
}

Il est tout à fait gourmand en mémoire, puisqu'un tampon flottant est nécessaire pour stocker les valeurs intermédiaires de l'image de destination (-> si on compte le canevas de résultat, on utilise 6 fois la mémoire de l'image source dans cet algorithme).
Il est également assez coûteux, puisque chaque pixel source est utilisé quelle que soit la taille de la destination, et nous devons payer pour le getImageData / putImageDate, assez lent également.
Mais il n'y a aucun moyen d'être plus rapide que de traiter chaque valeur source dans ce cas, et la situation n'est pas si mauvaise : pour mon image 740 * 556 d'un wombat, le traitement prend entre 30 et 40 ms.

0 votes

Serait-il plus rapide de mettre l'image à l'échelle avant de la placer dans le canevas ?

1 votes

Je ne comprends pas... il semble que c'est ce que je fais. Le tampon ainsi que le canevas que je crée (resCV) ont la taille de l'image mise à l'échelle. Je pense que la seule façon de l'obtenir plus rapidement serait d'utiliser un calcul entier de type breshensam. Mais 40ms n'est lent que pour un jeu vidéo (25 fps), pas pour une application de dessin.

0 votes

Voyez-vous une chance de rendre votre algorithme plus rapide tout en conservant la qualité ?

66voto

ViliusL Points 820

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

Mise à jour : la 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);
}

0 votes

J'ai besoin de la meilleure qualité

20 votes

Corrigé, j'ai changé "bon" en "meilleur", c'est bon maintenant ? :D. D'un autre côté, si vous voulez le meilleur rééchantillonnage possible, utilisez imagemagick.

0 votes

@confile imgur.com était sûr à utiliser dans jsfiddle, mais les admins ont fait quelque chose de mal ? Vous ne voyez pas la bonne qualité, parce que votre navigateur donne une erreur fatale CORS. (ne peut pas utiliser les images des sites distants)

31voto

Ken Fyrstenberg Points 38115

Suggestion 1 - étendre le tuyau de traitement

Vous pouvez utiliser le step-down comme je le décris dans les liens auxquels vous faites référence, mais vous semblez les utiliser de manière erronée.

La réduction d'échelle n'est pas nécessaire pour mettre à l'échelle des images à des rapports supérieurs à 1:2 (généralement, mais pas uniquement). C'est là que vous devez faire un drastique La réduction d'échelle doit être divisée en deux étapes (et rarement plus) en fonction du contenu de l'image (en particulier là où se trouvent les hautes fréquences comme les lignes fines).

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 obtenue soit aussi claire que l'original.

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

Essayez avec une seule étape supplémentaire, ou au mieux deux.

Convolutions

Dans le cas de Photoshop, remarquez qu'il applique une convolution après le ré-échantillonnage de l'image, comme l'accentuation. Ce n'est pas seulement l'interpolation bi-cubique qui a lieu, donc afin d'émuler complètement Photoshop, nous devons également ajouter les étapes que Photoshop effectue (avec la configuration par défaut).

Pour cet exemple, je vais utiliser ma réponse originale à laquelle vous faites référence dans votre message, mais j'y ai ajouté une convolution d'accentuation de la netteté pour améliorer la qualité en tant que post-traitement (voir la démo en bas).

Voici le code pour ajouter le filtre sharpen (il est basé sur un filtre de convolution générique - j'y ai mis la matrice de poids pour le sharpen ainsi qu'un facteur de mélange pour ajuster la prononciation de l'effet) :

Utilisation :

sharpen(context, width, height, mixFactor);

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

Fonction (sur la base de 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 :

DÉMONSTRATION EN LIGNE ICI

Result downsample and sharpen convolution

En fonction du degré d'accentuation que vous souhaitez ajouter au mélange, vous pouvez obtenir des résultats allant de "flous" par défaut à très nets :

Variations of sharpen

Suggestion 2 - mise en œuvre d'un algorithme de bas niveau

Si vous voulez obtenir le meilleur résultat en termes de qualité, vous devez aller au plus bas niveau et envisager de mettre en œuvre, par exemple, ce tout nouvel algorithme pour y parvenir.

Voir Rééchantillonnage d'images en fonction de l'interpolation (2011) de l'IEEE.
Voici un lien vers le document dans son intégralité (PDF) .

Il n'y a pas d'implémentation de cet algorithme en JavaScript à l'heure actuelle, donc vous en aurez plein les bras si vous voulez vous lancer dans cette tâche.

L'essentiel est (extraits du document) :

Résumé

Un algorithme de sous-échantillonnage adaptatif orienté par interpolation est proposé. pour le codage d'images à faible débit dans cet article. Étant donné une image, l'algorithme l'algorithme proposé est capable d'obtenir une image à faible résolution, à partir de laquelle une image de haute qualité peut être codée. résolution, à partir de laquelle une image de haute qualité, de même résolution que l'image l'image d'entrée peut être interpolée. Contrairement aux algorithmes traditionnels de algorithmes de sous-échantillonnage traditionnels, qui sont indépendants du processus d'interpolation. processus d'interpolation, l'algorithme de sous-échantillonnage proposé articule le d'interpolation, l'algorithme de sous-échantillonnage proposé lie le sous-échantillonnage au processus d'interpolation. Par conséquent, l'algorithme de l'algorithme de sous-échantillonnage proposé est capable de maintenir les informations l'information originale de l'image d'entrée dans la plus grande mesure. L'image sous-échantillonnée est ensuite introduite dans le format JPEG. Un post-traitement basé sur la variation totale (TV) est ensuite appliqué à l'image basse résolution décompressée. Enfin, l'image traitée est interpolée pour conserver la résolution originale de l'image d'entrée. résolution originale de l'image d'entrée. Les résultats expérimentaux vérifient que l'utilisation de l'image sous-échantillonnée par l'algorithme proposé permet d'obtenir une image interpolée de bien meilleure qualité. une image interpolée de bien meilleure qualité peut être obtenue. De plus, l'algorithme proposé est capable d'obtenir des performances supérieures à celles de JPEG pour le codage d'images à faible débit binaire.

Snapshot from paper

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

0 votes

C'est également une excellente solution. Merci !

0 votes

C'est une excellente solution. Je l'ai essayé sur des fichiers png avec des zones transparentes. Voici le résultat : jsfiddle.net/confile/5CD4N Avez-vous une idée de ce qu'il faut faire pour que ça marche ?

1 votes

C'est GENIUS ! mais s'il vous plaît pouvez-vous expliquer ce que vous faites exactement ? lol... je suis totalement désireux de connaître les tenants et les aboutissants... peut-être des ressources pour apprendre ?

21voto

Vitaly Points 190

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

Pica n'a pas encore de masque unsharp, mais cela sera ajouté très bientôt. C'est beaucoup plus facile que d'implémenter un filtre de convolution à haute vitesse pour le redimensionnement.

16voto

Robusto Points 17430

Pourquoi utiliser le canevas pour redimensionner les images ? Les navigateurs modernes utilisent tous l'interpolation bicubique - le même processus que celui utilisé par Photoshop (si vous le faites correctement) - et ils le font plus rapidement que le processus du canevas. Il suffit de spécifier la taille d'image souhaitée (n'utilisez qu'une seule dimension, la hauteur ou la largeur, pour un redimensionnement proportionnel).

Cette fonction est prise en charge par la plupart des navigateurs, y compris les dernières versions d'IE. Versions antérieures peut nécessiter un CSS spécifique au navigateur .

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

Il suffit ensuite d'utiliser la valeur renvoyée pour redimensionner l'image dans une ou deux dimensions.

Il est évident qu'il y a d'autres raffinements que vous pourriez faire, mais cela fait l'affaire.

Collez le code suivant dans la console de cette page et regardez ce qui se passe avec les 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";
});

2 votes

Notez également que si vous ne spécifiez qu'une seule dimension, le navigateur (moderne) conservera automatiquement le rapport d'aspect naturel de l'image.

1 votes

@Andre : Je l'ai noté dans mon premier paragraphe.

0 votes

@Andre : Notez également que l'approche unidimensionnelle ne fonctionne que si l'autre dimension n'a pas été spécifiée. En d'autres termes, si l'autre dimension a a été spécifié, vous devez faire les deux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X