Formaat van afbeelding wijzigen met javascript canvas (soepel)

Ik probeer het formaat van sommige afbeeldingen met canvas te wijzigen, maar ik heb geen idee hoe ik ze vloeiender kan maken.
Op photoshop, browsers enz.. zijn er een paar algoritmen die ze gebruiken (bijv. bicubisch, bilineair), maar ik weet niet of deze in canvas zijn ingebouwd of niet.

Hier is mijn viool: 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);

De eerste is een afbeeldingstag met een normale grootte en de tweede is canvas. Merk op dat het canvas niet zo glad is. Hoe kan ik ‘gladheid’ bereiken?


Antwoord 1, autoriteit 100%

Je kunt down-stepping gebruiken om betere resultaten te behalen. De meeste browsers lijken lineaire interpolatie te gebruiken in plaats van bi-cubicbij het wijzigen van het formaat van afbeeldingen.

(UpdateEr is een kwaliteitseigenschap toegevoegd aan de specificaties, imageSmoothingQualitydie momenteel alleen beschikbaar is in Chrome.)

Tenzij men geen afvlakking of naaste buur kiest, zal de browser de afbeelding altijd interpoleren nadat deze is verkleind, aangezien deze functie als een laagdoorlaatfilter fungeert om aliasing te voorkomen.

Bi-lineair gebruikt 2×2 pixels om de interpolatie uit te voeren, terwijl bi-cubic 4×4 gebruikt, dus door het in stappen te doen, kunt u dicht bij het bi-cubic resultaat komen terwijl u bi-lineaire interpolatie gebruikt zoals te zien is in de resulterende afbeeldingen.

>

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>

Antwoord 2, autoriteit 30%

Sinds Trung Le Nguyen Nhat’s vioolniet helemaal correct
(het gebruikt alleen de originele afbeelding in de laatste stap)
Ik schreef mijn eigen algemene viool met prestatievergelijking:

FIDDLE

Eigenlijk is het:

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

Antwoord 3, Autoriteit 11%

Ik heb een herbruikbare hoekige service gemaakt om de grootte van de grootte van de grootte van afbeeldingen / doeken te hanteren voor iedereen die geïnteresseerd is: https://gist.github.com/transitive-bullshit/37bac5e741eac60e983

De service bevat twee oplossingen omdat ze allebei hun eigen voors / nadelen hebben. De LANCZOS-convolutiebenadering is een hogere kwaliteit ten koste van langzamer, terwijl de stapsgewijze downscaling-aanpak redelijk antiiased-resultaten produceert en aanzienlijk sneller is.

Voorbeeld Gebruik:

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

Antwoord 4, Autoriteit 7%

Hoewel sommige van die code-fragmenten kort en werken, zijn ze niet triviaal om te volgen en te begrijpen.

Zoals ik geen fan ben van “Copy-Pure” van Stack-Overflow, zou ik willen dat ontwikkelaars de code begrijpen die ze in de software innen, hoop dat u het onderstaande nuttig vindt.

demo : het wijzigen van afbeeldingen met JS en HTML Canvas Demo Fiddler.

U kunt 3 verschillende methoden vinden om dit formaat te doen, dat zal u helpen begrijpen hoe de code werkt en waarom.

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

volledige code van zowel demo- en typencript-methode die u mogelijk in uw code wilt gebruiken, is te vinden in het GitHub-project.

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

Dit is de definitieve code:

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

Antwoord 5, autoriteit 4%

Ik begrijp niet waarom niemand createImageBitmapvoorstelt.

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

werkt prachtig (ervan uitgaande dat je id’s voor afbeelding en canvas instelt).


Antwoord 6, autoriteit 3%

Ik heb een bibliotheek gemaakt waarmee je elk percentage kunt verlagen terwijl je alle kleurgegevens behoudt.

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

Dat bestand kunt u in de browser opnemen. De resultaten zien eruit als Photoshop of Image Magick, behouden alle kleurgegevens, middelings pixels, in plaats van in de buurt te nemen en anderen te laten vallen. Het gebruikt geen formule om de gemiddelden te raden, het kost het exacte gemiddelde.


Antwoord 7, Autoriteit 3%

Op basis van K3N-antwoord, herschrijf ik code in het algemeen voor iedereen die

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

Update JSFiddle Demo

Hier is mijn online demo


Antwoord 8

Ik schreef klein JS-hulpprogramma om te oogsten en de grootte van het beeld aan de voorkant te vergroten. Hier is link op GitHub-project. Ook kunt u BLOB van de laatste afbeelding krijgen om het te verzenden.

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;

Antwoord 9

Hier is mijn code, waarvan ik hoop dat deze nuttig kan zijn voor iemand in de SO-gemeenschap:

U kunt uw doelafbeeldingsdimensie als parameter opnemen in uw scriptaanroep. Dat is de resultaatwaarde van de breedte of hoogte van uw afbeelding, welke groter is. De kleinere afmeting wordt aangepast, zodat de beeldverhouding van uw afbeelding ongewijzigd blijft. Je kunt ook je standaard doelgrootte hard coderen in het script.

U kunt het script eenvoudig aanpassen aan uw specifieke behoeften, zoals het gewenste afbeeldingstype (standaard is “afbeelding/png”) voor een uitvoer en beslissen in hoeveel stappen u procentueel het formaat van uw afbeelding wilt wijzigen voor een fijner resultaat (zie const procentStep in code).

  const ResizeImage = ( _ => {
const MAX_LENGTH = 260;     // default target size of largest dimension, either witdth or height
const percentStep = .3;     // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();
const doResize = (callback, maxLength) => {
    // abort with error if image has a dimension equal to zero
    if(image.width == 0 || image.height == 0) {
        return {blob: null, error: "either image width or height was zero "};
    }
    // use caller dimension or default length if none provided
    const length = maxLength == null  ? MAX_LENGTH : maxLength;
    canvas.width = image.width;
    canvas.height = image.height;
    canvasContext.drawImage(image, 0, 0, image.width, image.height);
    // if image size already within target size, just copy and return blob
    if(image.width <= length && image.height <= length) {
        canvas.toBlob( blob => {
            callback({ blob: blob, error: null });
        }, "image/png", 1);
        return;
    }
    var startDim = Math.max(image.width, image.height);
    var startSmallerDim = Math.min(image.width, image.height);
    // gap to decrease in size until we reach the target size,
    // be it by decreasing the image width or height,
    // whichever is largest
    const gap = startDim - length;
    // step length of each resizing iteration
    const step = parseInt(percentStep*gap);
    //  no. of iterations
    var nSteps = 0;
    if(step == 0) {
        step = 1;
    } else {
        nSteps = parseInt(gap/step);
    }
    // length of last additional resizing step, if needed
    const lastStep = gap % step;
    // aspect ratio = value by which we'll  multiply the smaller dimension
    // in order to keep the aspect ratio unchanged in each iteration
    const ratio = startSmallerDim/startDim;
    var newDim;          // calculated new length for the bigger dimension of the image, be it image width or height
    var smallerDim;     // length along the smaller dimension of the image, width or height
    for(var i = 0; i < nSteps; i++) {
        // decrease longest dimension one step in pixels
        newDim = startDim - step;
        // decrease shortest dimension proportionally, so as to keep aspect ratio
        smallerDim = parseInt(ratio*newDim);
        // assign calculated vars to their corresponding canvas dimension, width or height
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [newDim, smallerDim];
        } else {
            [canvas.width, canvas.height] = [smallerDim, newDim];
        }
        // draw image one step smaller
        canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
        // cycle var startDim for new loop
        startDim = newDim;
    }
    // do last missing resizing step to finally reach target image size
    if(lastStep > 0) {
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
        } else {
            [canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
        }
        canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
    }
    // send blob to caller
    canvas.toBlob( blob => {
        callback({blob: blob, error: null});
    }, "image/png", 1);
};
const resize = async (imgSrc, callback, maxLength) => {
    image.src = imgSrc;
    image.onload = _ => {
       doResize(callback, maxLength);
    };
};
return { resize: resize }
})();

Gebruik:

ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
    if(imageObject.error != null) {
      // handle errors here
      console.log(imageObject.error);
      return;
    }
    // do whatever you want with the blob, like assinging it to
    // an img element, or uploading it to a database
    // ...
    document.querySelector("#my-image").src = imageObject.blob;
    // ...
}, 300);

Antwoord 10

Ik heb dit opgelost door schaal te gebruiken voor canvas en de beeldkwaliteit in mijn geval wordt echt goed.

Dus eerst schaal ik de inhoud binnen van canvas:

ctx.scale(2, 2)

en schaal vervolgens het canvas-tag op met CSS:

#myCanvas { transform: scale(0.5); }

Other episodes