Redimensionner l'image avec le canevas javascript (en douceur)

90

J'essaie de redimensionner certaines images avec une toile, mais je ne sais pas comment les lisser. Sur photoshop, navigateurs etc. il y a quelques algorithmes qu'ils utilisent (par exemple bicubique, bilinéaire) mais je ne sais pas si ceux-ci sont intégrés dans le canevas ou non.

Voici mon violon: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

Le premier est une balise d'image redimensionnée normale, et le second est un canevas. Remarquez que la toile n'est pas aussi lisse. Comment puis-je obtenir une «douceur»?

Steve
la source

Réponses:

136

Vous pouvez utiliser la descente pour obtenir de meilleurs résultats. La plupart des navigateurs semblent utiliser l'interpolation linéaire plutôt que bi-cubique lors du redimensionnement des images.

( Mise à jour Une propriété de qualité a été ajoutée aux spécifications, imageSmoothingQualityqui n'est actuellement disponible que dans Chrome.)

À moins que l'on ne choisisse pas de lissage ou de voisin le plus proche, le navigateur interpolera toujours l'image après l'avoir réduite comme cette fonction comme un filtre passe-bas pour éviter l'aliasing.

Le bi-linéaire utilise 2x2 pixels pour faire l'interpolation tandis que le bi-cubique utilise le 4x4 donc en le faisant par étapes, vous pouvez vous rapprocher du résultat bi-cubique tout en utilisant l'interpolation bi-linéaire comme on le voit dans les images résultantes.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

En fonction de la rigueur de votre redimensionnement, vous pouvez sauter l'étape 2 si la différence est moindre.

Dans la démo, vous pouvez voir que le nouveau résultat est maintenant très similaire à l'élément image.


la source
1
@steve heh, parfois ces choses se produisent :) Pour les images, vous pouvez généralement remplacer cela en définissant un css BTW.
Ken, le premier résultat a très bien fonctionné mais quand je change les images, vous pouvez le voir être trop flou jsfiddle.net/kcHLG Que peut-on faire dans ce cas et dans d'autres?
steve
@steve vous pouvez réduire le nombre d'étapes à seulement 1 ou aucune (pour certaines images, cela fonctionne bien). Voir aussi cette réponse qui est similaire à celle-ci, mais ici, j'ai ajouté une convolution de netteté afin que vous puissiez rendre l'image résultante plus nette après sa réduction.
1
@steve ici est un violon modifié avec Bill en utilisant une seule étape supplémentaire: jsfiddle.net/AbdiasSoftware/kcHLG/1
1
@neaumusic le code est une continuation du code OP. Si vous ouvrez le violon, vous verrez ctx en cours de définition. Je l'ai inséré ici pour éviter les malentendus.
27

Puisque le violon de Trung Le Nguyen Nhat n'est pas du tout correct (il utilise juste l'image originale dans la dernière étape)
j'ai écrit mon propre violon général avec comparaison des performances:

VIOLON

En gros, c'est:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
Sébastien Ott
la source
Réponse la plus sous-estimée jamais vue.
Amsakanna
17

J'ai créé un service Angular réutilisable pour gérer le redimensionnement de haute qualité des images / toiles pour tous ceux qui sont intéressés: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Le service comprend deux solutions car elles ont toutes les deux leurs propres 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 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
    })
})
fisch2
la source
Désolé, j'ai changé mon nom d'utilisateur github. Je viens de mettre à jour le lien gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2
3
J'ai vu le mot angulaire, j'ai eu cette drôle de sensation
SuperUberDuper
8

Bien que certains de ces extraits de code soient courts et efficaces, ils ne sont pas simples à suivre et à comprendre.

Comme je ne suis pas fan du "copier-coller" de stack-overflow, j'aimerais que les développeurs comprennent le code qu'ils introduisent dans leur logiciel, j'espère que vous trouverez ce qui suit utile.

DEMO : Redimensionner des images avec JS et HTML Canvas Demo Fiddler.

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

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

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

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

Voici 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 dize 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
        }
    };
}}
Eyal c
la source
4

J'ai créé une bibliothèque qui vous permet d'abaisser n'importe quel pourcentage tout en conservant toutes les données de couleur.

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

Ce fichier que vous pouvez inclure dans le navigateur. Les résultats ressembleront à Photoshop ou à la magie de l'image, préservant toutes les données de couleur, calculant la moyenne des pixels, plutôt que de prendre ceux qui se trouvent à proximité et d'en laisser tomber d'autres. Il n'utilise pas de formule pour deviner les moyennes, il prend la moyenne exacte.

Funkodebat
la source
1
J'utiliserais probablement webgl pour redimensionner maintenant
Funkodebat
4

Sur la base de la réponse K3N, je réécris le code généralement pour quiconque veut

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

MISE À JOUR DE LA DÉMO JSFIDDLE

Voici ma DEMO EN LIGNE

Trung Le Nguyen Nhat
la source
2
Cela ne fonctionnera pas: chaque fois que vous redimensionnez le canevas, cela effacera son contexte. Vous avez besoin de 2 toiles. Ici, c'est juste la même chose que d'appeler directement drawImage avec les dimensions finales.
Kaiido le
2

Je ne comprends pas pourquoi personne ne suggère createImageBitmap.

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

fonctionne à merveille (en supposant que vous définissez des identifiants pour l'image et le canevas).

cagdas_ucar
la source
Parce que ce n'est pas largement pris en charge caniuse.com/#search=createImageBitmap
Matt
createImageBitmap est pris en charge par 73% de tous les utilisateurs. Selon votre cas d'utilisation, cela peut être suffisant. C'est juste Safari qui refuse d'ajouter le support pour cela. Je pense que cela vaut la peine d'être mentionné comme solution possible.
cagdas_ucar
Belle solution, mais cela ne fonctionne pas sur Firefox malheureusement
vcarel
1

J'ai écrit un petit utilitaire js pour recadrer et redimensionner l'image sur le front-end. Voici le lien sur le projet GitHub. Vous pouvez également obtenir le blob de l'image finale pour l'envoyer.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Diyaz Yakubov
la source