325 votes

Redimensionnement d'une image dans un canevas HTML5

J'essaie de créer une image miniature du côté client à l'aide de javascript et d'un élément canvas, mais lorsque je réduis l'image, elle a un aspect terrible. On dirait qu'elle a été réduite dans Photoshop avec un rééchantillonnage réglé sur "Nearest Neighbor" au lieu de Bicubic. Je sais qu'il est possible d'obtenir un aspect correct, car ce site peut le faire très bien en utilisant une toile également. J'ai essayé d'utiliser le même code que celui qu'ils utilisent, comme indiqué dans le lien "[Source]", mais l'apparence est toujours aussi mauvaise. Y a-t-il quelque chose qui m'échappe, un paramètre à définir ou autre ?

EDITAR:

J'essaie de redimensionner un fichier jpg. J'ai essayé de redimensionner le même fichier jpg sur le site lié et dans Photoshop, et il semble correct lorsqu'il est réduit.

Voici le code correspondant :

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2 :

Il semble que je me sois trompé, le site web lié ne faisait pas un meilleur travail pour réduire la taille de l'image. J'ai essayé les autres méthodes suggérées et aucune d'entre elles n'est meilleure. Voici le résultat des différentes méthodes :

Photoshop :

alt text

Toile :

alt text

Image avec rendu d'image : optimizeQuality défini et mis à l'échelle avec largeur/hauteur :

alt text

Image avec image-rendering : optimizeQuality set et mise à l'échelle avec -moz-transform :

alt text

Redimensionnement de la toile sur pixastic :

alt text

Je suppose que cela signifie que Firefox n'utilise pas l'échantillonnage bicubique comme il est censé le faire. Je vais devoir attendre qu'ils l'ajoutent vraiment.

EDIT3 :

Image originale

0 votes

Pouvez-vous afficher le code que vous utilisez pour redimensionner l'image ?

0 votes

Essayez-vous de redimensionner une image GIF ou une image similaire avec une palette limitée ? Même dans Photoshop, ces images ne se réduisent pas bien à moins de les convertir en RVB.

0 votes

Pouvez-vous poster une copie de l'image originale ?

404voto

syockit Points 3256

Alors que faire si tous les navigateurs (en fait, Chrome 5 m'en a donné un assez bon) ne vous donnent pas une qualité de rééchantillonnage suffisante ? Vous les implémentez vous-même alors ! Oh allez, nous entrons dans la nouvelle ère du Web 3.0, des navigateurs compatibles HTML5, des compilateurs javascript JIT super optimisés, des machines multi-core(†), avec des tonnes de mémoire, de quoi avez-vous peur ? Hé, il y a le mot java dans javascript, donc ça devrait garantir les performances, non ? Voici le code de génération des vignettes :

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

...avec lequel vous pouvez produire des résultats comme ceux-ci !

img717.imageshack.us/img717/8910/lanczos358.png

Quoi qu'il en soit, voici une version "corrigée" de votre exemple :

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

Il est maintenant temps de mettre en concurrence vos meilleurs navigateurs et de voir lequel fera le moins augmenter la pression sanguine de vos clients !

Umm, où est ma balise de sarcasme ?

(étant donné que de nombreuses parties du code sont basées sur Galerie Anrieff Générateur est-il également couvert par la GPL2 ? Je ne sais pas)

En fait, à cause des limitations de javascript, le multi-core n'est pas supporté.

2 votes

J'avais en fait essayé de l'implémenter moi-même, en faisant comme vous, en copiant le code d'un éditeur d'images open source. Comme je n'ai pas pu trouver de documentation solide sur l'algorithme, j'ai eu du mal à l'optimiser. Au final, le mien était plutôt lent (il fallait quelques secondes pour redimensionner l'image). Quand j'en aurai l'occasion, j'essaierai le vôtre pour voir s'il est plus rapide. Et je pense que les webworkers rendent le javascript multi-core possible maintenant. J'allais essayer de les utiliser pour accélérer le processus, mais j'avais du mal à trouver comment en faire un algorithme multithread.

0 votes

On dirait que vous avez oublié un bout de code. Il semble manquer la fonction lanczosCreate(). Pourriez-vous modifier votre message pour l'inclure, afin que je puisse tester votre code ?

3 votes

Désolé, j'ai oublié ça ! J'ai modifié la réponse. Ce ne sera pas rapide de toute façon, le bicubique devrait être plus rapide. Sans compter que l'algorithme que j'ai utilisé n'est pas le redimensionnement habituel à 2 voies (qui est ligne par ligne, horizontal puis vertical), donc c'est beaucoup plus lent.

41voto

ViliusL Points 820

Algorithme rapide de redimensionnement/rééchantillonnage d'image utilisant Hermite avec JavaScript. Supporte la transparence, donne une bonne qualité. Prévisualisation :

enter image description here

Mise à jour : version 2.0 ajoutée sur GitHub (plus rapide, web workers + objets transférables). Enfin je l'ai fait fonctionner !

Git : https://github.com/viliusle/Hermite-resize
Démonstration : http://viliusle.github.io/miniPaint/

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

Peut-être pouvez-vous inclure des liens vers votre démo miniPaint et votre dépôt Github ?

1 votes

Allez-vous également partager la version pour les travailleurs du web ? Probablement à cause des frais de configuration, c'est plus lent pour les petites images, mais cela pourrait être utile pour les images sources plus grandes.

0 votes

J'ai ajouté la démo, les liens git, ainsi que la version multi-core. En fait, je n'ai pas passé trop de temps à optimiser la version multicore... Je crois que la version simple est bien optimisée.

15voto

cesarsalazar Points 568

Je sais qu'il s'agit d'un vieux fil de discussion mais il pourrait être utile pour certaines personnes comme moi qui, quelques mois après, rencontrent ce problème pour la première fois.

Voici un code qui redimensionne l'image à chaque fois que vous la rechargez. Je suis conscient que ce n'est pas du tout optimal, mais je le fournis comme preuve de concept.

Je suis également désolé d'utiliser jQuery pour les sélecteurs simples, mais je me sens trop à l'aise avec la syntaxe.

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};

html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

Ma fonction createImage est appelée une fois lorsque le document est chargé, puis elle est appelée chaque fois que la fenêtre reçoit un événement de redimensionnement.

Je l'ai testé dans Chrome 6 et Firefox 3.6, tous deux sur Mac. Cette "technique" mange le processeur comme si c'était une glace en été, mais elle fait l'affaire.

10voto

Daniel Points 1032

J'ai mis en place quelques algorithmes pour faire de l'interpolation d'images sur des tableaux de pixels de canevas html qui pourraient être utiles ici :

https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

Ils peuvent être copiés/collés et peuvent être utilisés à l'intérieur des web workers pour redimensionner les images (ou toute autre opération qui nécessite une interpolation - je les utilise pour défigurer des images en ce moment).

Je n'ai pas ajouté les éléments lanczos ci-dessus, alors n'hésitez pas à les ajouter à titre de comparaison si vous le souhaitez.

5voto

Xavi Points 9768

Si vous essayez simplement de redimensionner une image, je vous recommande de définir le paramètre width y height de l'image avec CSS. Voici un exemple rapide :

.small-image {
    width: 100px;
    height: 100px;
}

Notez que le height y width peut également être définie à l'aide de JavaScript. Voici un exemple de code rapide :

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

De plus, pour s'assurer que l'image redimensionnée a une bonne apparence, ajoutez les règles css suivantes au sélecteur d'image :

D'après ce que je sais, tous les navigateurs, à l'exception d'IE, utilisent par défaut un algorithme bicubique pour redimensionner les images. Vos images redimensionnées devraient donc avoir une bonne apparence dans Firefox et Chrome.

Si vous définissez le css width y height ne fonctionne pas, vous pouvez jouer avec un css transform :

Si pour une raison quelconque vous besoin de pour utiliser un canevas, veuillez noter qu'il existe deux façons de redimensionner une image : en redimensionnant le canevas avec css ou en dessinant l'image à une taille plus petite.

Ver cette question pour plus de détails.

5 votes

Malheureusement, ni le redimensionnement de la toile ni le dessin de l'image à une taille plus petite ne résolvent le problème (dans Chrome).

1 votes

Chrome 27 produit une belle image redimensionnée, mais vous ne pouvez pas copier le résultat dans un canevas ; en essayant de le faire, vous copiez l'image originale à la place.

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