Redimensionner une image dans un canevas HTML5

314

J'essaie de créer une image miniature du côté client en utilisant javascript et un élément canvas, mais quand je rétrécis l'image, ça a l'air terrible. Il semble qu'il ait été réduit dans Photoshop avec le rééchantillonnage défini sur `` Voisin le plus proche '' au lieu de Bicubic. Je sais qu'il est possible de faire en sorte que cela soit correct, car ce site peut très bien le faire en utilisant également une toile. J'ai essayé d'utiliser le même code que celui indiqué dans le lien "[Source]", mais il a toujours l'air terrible. Y a-t-il quelque chose qui me manque, un paramètre qui doit être défini ou quelque chose?

ÉDITER:

J'essaie de redimensionner un jpg. J'ai essayé de redimensionner le même jpg sur le site lié et dans photoshop, et il a l'air bien lorsqu'il est réduit.

Voici le code pertinent:

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:

On dirait que je me suis trompé, le site Web lié ne faisait pas mieux de 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. C'est ce que les différentes méthodes ont abouti:

Photoshop:

texte alternatif

Toile:

texte alternatif

Image avec rendu d'image: optimisationQualité définie et mise à l'échelle avec largeur / hauteur:

texte alternatif

Image avec rendu d'image: optimisationQualité définie et mise à l'échelle avec -moz-transform:

texte alternatif

Redimensionnement de la toile sur pixastic:

texte alternatif

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

EDIT3:

Image originale

Telanor
la source
Pouvez-vous publier le code que vous utilisez pour redimensionner l'image?
Xavi
Essayez-vous de redimensionner une image GIF ou une image similaire avec une palette limitée? Même dans Photoshop, ces images ne diminuent pas bien à moins que vous ne les convertissiez en RVB.
leepowers
Pouvez-vous publier une copie de l'image originale?
Xavi
Redimensionner l'image à l'aide de javascript est un peu compliqué - non seulement vous utilisez la puissance de traitement client pour redimensionner l'image, mais vous le faites à chaque chargement de page. Pourquoi ne pas simplement enregistrer une version réduite de Photoshop et la servir à la place / en tandem avec l'image originale?
définit
29
Parce que je crée un téléchargeur d'images avec la capacité de redimensionner et de recadrer les images avant de les télécharger.
Telanor

Réponses:

393

Alors, que faites-vous si tous les navigateurs (en fait, Chrome 5 m'en ont donné un assez bon) ne vous offrent pas une qualité de rééchantillonnage suffisante? Vous les mettez alors en œuvre vous-même! 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 multicœurs (†), avec des tonnes de mémoire, de quoi avez-vous peur? Hé, il y a le mot java en javascript, donc ça devrait garantir les performances, non? Voici, le code de génération de 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

de toute façon, voici une version «fixe» 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 comparer vos meilleurs navigateurs et de voir lequel augmentera le moins la tension artérielle de votre client!

Umm, où est ma balise sarcasme?

(puisque de nombreuses parties du code sont basées sur Anrieff Gallery Generator, est-il également couvert par la GPL2? Je ne sais pas)

en raison de la limitation de javascript, le multicœur n'est pas pris en charge.

syockit
la source
2
J'avais en fait essayé de l'implémenter moi-même, en faisant comme vous, en copiant du code à partir 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 a fallu quelques secondes pour redimensionner l'image). Quand j'en aurai l'occasion, je vais essayer le vôtre et voir si c'est plus rapide. Et je pense que les webworkers rendent possible le javascript multi-core maintenant. J'allais essayer de les utiliser pour l'accélérer, mais j'avais du mal à comprendre comment en faire un algorithme multithread
Telanor
3
Désolé, j'ai oublié ça! J'ai édité la réponse. Ça ne va pas être rapide de toute façon, le bicubique devrait être plus rapide. Sans oublier que l'algorithme que j'ai utilisé n'est pas le redimensionnement bidirectionnel habituel (qui est ligne par ligne, horizontal puis vertical), c'est donc un looot plus lent.
syockit
5
Vous êtes génial et méritez des tonnes de super kilomètres.
Rocklan
5
Cela produit des résultats décents, mais prend 7,4 secondes pour une image de 1,8 MP dans la dernière version de Chrome ...
mpen
2
Comment des méthodes comme celle-ci parviennent-elles à un score aussi élevé ?? La solution présentée échoue complètement à prendre en compte l'échelle logarithmique utilisée pour stocker les informations de couleur. Un RVB de 127.127.127 est un quart de la luminosité de 255, 255, 255 et non la moitié. L'échantillonnage vers le bas dans la solution donne une image sombre. Dommage que cela soit fermé car il existe une méthode très simple et rapide pour réduire la taille qui produit des résultats encore meilleurs que l'échantillon Photoshop (OP doit avoir eu des préférences mal définies) donné
Blindman67
37

Algorithme de redimensionnement / rééchantillonnage d'image rapide utilisant un filtre Hermite avec JavaScript. Soutenir la transparence, donne une bonne qualité. Aperçu:

entrez la description de l'image ici

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

Git: https://github.com/viliusle/Hermite-resize
Démo: 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);
}
ViliusL
la source
Peut-être pouvez-vous inclure des liens vers votre démo miniPaint et votre dépôt Github?
syockit
1
Partagerez-vous également la version Webworkers? Probablement en raison de la surcharge de configuration, c'est plus lent pour les petites images, mais cela pourrait être utile pour les images source plus grandes.
syockit
démo ajoutée, liens git, également version multi-core. Btw je n'ai pas passé trop de temps à optimiser la version multicœur ... La version simple je crois est bien optimisée.
ViliusL
Grande différence et performances décentes. Merci beaucoup! avant et après
KevBurnsJr
2
@ViliusL Ah maintenant, je me souviens pourquoi les travailleurs du Web ne fonctionnaient pas aussi bien. Ils n'avaient pas de mémoire partagée auparavant et ne l'ont toujours pas maintenant! Peut-être qu'un jour, quand ils parviendront à le trier, votre code viendra à utiliser (cela, ou peut-être que les gens utiliseront plutôt PNaCl)
syockit
26

Essayez pica - c'est un resizer hautement optimisé avec des algorithmes sélectionnables. Voir la démo .

Par exemple, l'image originale du premier message est redimensionnée en 120 ms avec le filtre Lanczos et la fenêtre 3px ou 60ms avec le filtre Box et la fenêtre 0,5px. Pour une énorme image de 17 Mo, le redimensionnement de 5000 x 3000 pixels prend environ 1 seconde sur le bureau et 3 secondes sur le mobile.

Tous les principes de redimensionnement ont été très bien décrits dans ce fil, et pica n'ajoute pas la science des fusées. Mais il est très bien optimisé pour les JIT modernes et est prêt à être utilisé hors de la boîte (via npm ou bower). En outre, il utilise des webworkers lorsqu'ils sont disponibles pour éviter les gels d'interface.

Je prévois également d'ajouter bientôt un support de masque flou, car il est très utile après la réduction d'échelle.

Vitaly
la source
14

Je sais que c'est un vieux fil de discussion, mais il pourrait être utile pour certaines personnes comme moi que des mois plus tard, ce problème se pose pour la première fois.

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

Aussi, désolé d'utiliser jQuery pour des 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é et ensuite 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 s'il s'agissait de crème glacée en été, mais elle fait l'affaire.

cesarsalazar
la source
9

J'ai mis en place quelques algorithmes pour effectuer une interpolation d'image sur des tableaux de pixels de toile html qui pourraient être utiles ici:

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

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

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

Daniel
la source
6

Il s'agit d'une fonction javascript adaptée du code @ Telanor. Lors du passage d'une image base64 comme premier argument à la fonction, elle renvoie la base64 de l'image redimensionnée. maxWidth et maxHeight sont facultatifs.

function thumbnail(base64, maxWidth, maxHeight) {

  // Max size for thumbnail
  if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
  if(typeof(maxHeight) === 'undefined') var maxHeight = 500;

  // Create and initialize two canvas
  var canvas = document.createElement("canvas");
  var ctx = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");

  // Create original image
  var img = new Image();
  img.src = base64;

  // Determine new ratio based on max size
  var ratio = 1;
  if(img.width > maxWidth)
    ratio = maxWidth / img.width;
  else if(img.height > maxHeight)
    ratio = maxHeight / img.height;

  // Draw original image in second canvas
  canvasCopy.width = img.width;
  canvasCopy.height = img.height;
  copyContext.drawImage(img, 0, 0);

  // Copy and resize second canvas to first canvas
  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);

  return canvas.toDataURL();

}
Christophe Marois
la source
votre approche est très rapide mais elle produit une image floue comme vous pouvez le voir ici: stackoverflow.com/questions/18922880/…
confile
6

Je vous suggère fortement de vérifier ce lien et de vous assurer qu'il est défini sur true.

Contrôle du comportement de mise à l'échelle de l'image

Introduit dans Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 a introduit la propriété mozImageSmoothingEnabled à l'élément canvas; si cette valeur booléenne est fausse, les images ne seront pas lissées lors de leur mise à l'échelle. Cette propriété est vraie par défaut. voir plainprint?

  1. cx.mozImageSmoothingEnabled = false;
Evan Carroll
la source
5

Si vous essayez simplement de redimensionner une image, je recommanderais de définir widthet heightde l'image avec CSS. Voici un petit exemple:

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

Notez que le heightet widthpeut également être défini à 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 vous assurer que l'image redimensionnée semble bonne, ajoutez les règles css suivantes au sélecteur d'image:

Pour autant que je sache, tous les navigateurs sauf IE utilisent un algorithme bicubique pour redimensionner les images par défaut, de sorte que vos images redimensionnées devraient bien paraître dans Firefox et Chrome.

Si le réglage du CSS widthet heightne fonctionne pas, vous voudrez peut-être jouer avec un CSS transform:

Si pour une raison quelconque vous avez besoin d'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.

Voir cette question pour plus de détails.

Xavi
la source
5
Ni redimensionner la toile ni dessiner l'image à une taille plus petite ne résout malheureusement le problème (dans Chrome).
Nestor
1
Chrome 27 produit une belle image redimensionnée, mais vous ne pouvez pas copier le résultat sur un canevas; tenter de le faire copiera l'image originale à la place.
syockit
4

Pour redimensionner l'image avec une largeur inférieure à l'original, j'utilise:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

et ça marche =).

Yaffle
la source
4

j'ai obtenu cette image en cliquant avec le bouton droit sur l'élément canvas dans firefox et en l'enregistrant sous.

texte alternatif

var img = new Image();
img.onload = function () {
    console.debug(this.width,this.height);
    var canvas = document.createElement('canvas'), ctx;
    canvas.width = 188;
    canvas.height = 150;
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';

de toute façon, voici une version «fixe» de votre exemple:

var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);

var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body

document.body.appendChild(canvasCopy);

var copyContext = canvasCopy.getContext("2d");

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

        // defining cause it wasnt
        var maxWidth = 188,
            maxHeight = 150;

        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;
        // the line to change
        // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        // the method signature you are using is for slicing
        ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};

// changed for example
img.src = 'original.jpg';
robert
la source
J'ai essayé de faire ce que tu as fait et ça ne sort pas bien comme le tien. Sauf si j'ai raté quelque chose, le seul changement que vous avez fait a été d'utiliser la signature de la méthode de mise à l'échelle au lieu de celle de la découpe, non? Pour une raison quelconque, cela ne fonctionne pas pour moi.
Telanor
3

Le problème avec certaines de ces solutions est qu'elles accèdent directement aux données de pixels et les parcourent pour effectuer le sous-échantillonnage. Selon la taille de l'image, cela peut être très gourmand en 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, le plus proche voisin. Cela fonctionne bien lorsque vous ne redimensionnez pas plus de la moitié de la taille d'origine .

Si vous bouclez pour ne redimensionner que la moitié au maximum à la fois, les résultats seraient assez bons et beaucoup plus rapides que l'accès aux données de pixels.

Cette fonction sous-échantillonne la 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

Jesús Carrera
la source
2

Donc, quelque chose d'intéressant que j'ai trouvé il y a quelque temps en travaillant avec un canevas qui pourrait être utile:

Pour redimensionner le contrôle de canevas seul, vous devez utiliser les attributs height=""et width=""(ou canvas.width/ les canvas.heightéléments). Si vous utilisez CSS pour redimensionner le canevas, il étirera (c.-à-d.: Redimensionner) le contenu du canevas pour l'adapter au canevas complet (plutôt que d'augmenter ou de diminuer simplement la zone du canevas).

Il vaudrait la peine d'essayer de dessiner l'image dans un contrôle de canevas avec les attributs de hauteur et de largeur définis à la taille de l'image, puis d'utiliser CSS pour redimensionner le canevas à la taille que vous recherchez. Peut-être que cela utiliserait un algorithme de redimensionnement différent.

Il convient également de noter que le canevas a des effets différents dans différents navigateurs (et même dans différentes versions de différents navigateurs). Les algorithmes et techniques utilisés dans les navigateurs sont susceptibles de changer avec le temps (en particulier avec Firefox 4 et Chrome 6 qui sortiront si tôt, ce qui mettra fortement l'accent sur les performances de rendu de la toile).

En outre, vous pouvez également essayer SVG, car il utilise probablement un algorithme différent également.

Bonne chance!

mattbasta
la source
1
La définition de la largeur ou de la hauteur d'un canevas via les attributs HTML entraîne l'effacement du canevas, donc aucun redimensionnement ne peut être effectué avec cette méthode. En outre, SVG est destiné à traiter des images mathématiques. J'ai besoin de pouvoir dessiner des fichiers PNG et autres, pour que cela ne m'aide pas.
Telanor
Définir la hauteur et la largeur du canevas et redimensionner à l'aide de CSS n'aide pas, j'ai trouvé (dans Chrome). Même faire le redimensionnement en utilisant -webkit-transform plutôt que la largeur / hauteur CSS ne déclenche pas l'interpolation.
Nestor
2

J'ai l'impression que le module que j'ai écrit produira des résultats similaires à Photoshop, car il préserve les données de couleur en les moyennant, sans appliquer d'algorithme. C'est un peu lent, mais pour moi, c'est le meilleur, car il préserve toutes les données de couleur.

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

Il ne prend pas le voisin le plus proche et ne supprime pas d'autres pixels, ou échantillonne un groupe et prend une moyenne aléatoire. Il prend la proportion exacte que chaque pixel source doit produire dans le pixel de destination. La couleur moyenne des pixels dans la source sera la couleur moyenne des pixels dans la destination, ce que ces autres formules, je pense, ne seront pas.

un exemple d'utilisation est au bas de https://github.com/danschumann/limby-resize

MISE À JOUR OCT 2018 : De nos jours, mon exemple est plus académique qu'autre chose. Webgl est à peu près à 100%, il vaut donc mieux redimensionner avec cela pour produire des résultats similaires, mais plus rapidement. PICA.js fait cela, je crois. -

Funkodebat
la source
1

J'ai converti la réponse de @ syockit ainsi que l'approche descendante en un service Angular réutilisable pour tous ceux qui sont intéressés: https://gist.github.com/fisch0920/37bac5e741eaec60e983

J'ai inclus les deux solutions car elles ont chacune leurs avantages / inconvénients. L'approche de convolution lanczos est de meilleure qualité au prix d'être plus lente, tandis que l'approche de réduction d'échelle par étapes produit des résultats raisonnablement anti-crénelés et est beaucoup 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
    })
})
fisch2
la source
1

Resizer d'image Javascript simple et rapide:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

L'histoire

C'est vraiment après de nombreuses séries de recherches, de lecture et d'essais.

L'algorithme de redimensionnement utilise le script Hermite de @ ViliusL (le redimensionneur Hermite est vraiment le plus rapide et donne une sortie raisonnablement bonne). Etendu avec les fonctionnalités dont vous avez besoin.

Forks 1 travailleur pour effectuer le redimensionnement afin qu'il ne gèle pas votre navigateur lors du redimensionnement, contrairement à tous les autres redimensionneurs JS.

Calvintwr
la source
0

Merci @syockit pour une réponse géniale. cependant, j'ai dû reformater un peu comme suit pour le faire fonctionner. Peut-être en raison de problèmes de numérisation DOM:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

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(process1, 0, this, 0);
}

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

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(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    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";
}
});
Manish
la source
-1

Je viens de lancer une page de comparaisons côte à côte et à moins que quelque chose n'ait changé récemment, je ne pouvais pas voir de meilleure réduction des effectifs (mise à l'échelle) en utilisant canvas vs css simple. J'ai testé dans FF6 Mac OSX 10.7. Toujours légèrement doux par rapport à l'original.

Cependant, je suis tombé sur quelque chose qui a fait une énorme différence et qui était d'utiliser des filtres d'image dans les navigateurs qui prennent en charge le canevas. Vous pouvez réellement manipuler des images comme vous pouvez le faire dans Photoshop avec flou, netteté, saturation, ondulation, niveaux de gris, etc.

J'ai ensuite trouvé un plug-in jQuery génial qui rend l'application de ces filtres un jeu d'enfant: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

J'applique simplement le filtre de netteté juste après avoir redimensionné l'image, ce qui devrait vous donner l'effet souhaité. Je n'ai même pas eu à utiliser d'élément canvas.

Julian Dormon
la source
-1

Vous cherchez une autre excellente solution simple?

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

Cette solution utilisera l'algorithme de redimensionnement du navigateur! :)

ale500
la source
La question est de sous-échantillonner l'image, pas seulement de la redimensionner.
Jesús Carrera
[...] J'essaie de redimensionner un jpg. J'ai essayé de redimensionner le même jpg sur le site lié et dans photoshop, et il semble très bien quand la taille est réduite. [...] pourquoi vous ne pouvez pas utiliser le <img> Jesus Carrera?
ale500