javascript - Endre størrelsen på et bilde i en HTML5-canvas


Jeg prøver å lage et miniatyrbilde på klientsiden ved hjelp av JavaScript og et canvas-element, men når jeg krymper bildet ser det forferdelig ut. Det ser ut som om det ble redusert i Photoshop med omprøveinnstillingen satt til 'Nearest Neighbor' i stedet for Bicubic. Jeg vet at det er mulig å få dette til å se riktig ut, fordi dette nettstedet kan gjøre det helt fint ved å bruke et canvas også. Jeg har prøvd å bruke den samme koden de gjør som vist i "[Source]"-lenken, men det ser fortsatt forferdelig ut. Er det noe jeg mangler, en innstilling som må settes eller noe?

REDIGER :

Jeg prøver å endre størrelsen på en jpg. Jeg har prøvd å endre størrelsen på den samme jpg-en på den koblede siden og i Photoshop, og den ser bra ut når den gjøres mindre.

Her er den relevante koden:

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

REDIGER2:

Ser ut til at jeg tok feil, den lenkede nettsiden gjorde ikke en bedre jobb med å redusere bildestørrelsen. Jeg prøvde de andre foreslåtte metodene, og ingen av dem ser bedre ut. Dette er hva de forskjellige metodene resulterte i:

Photoshop:

Lerret :

Bilde med image-rendering: optimizeQuality satt og skalert med bredde/høyde:

Bilde med image-rendering: optimizeQuality satt og skalert med -moz-transform:

Endre størrelse på lerret i Pixastic:

Jeg antar dette betyr at Firefox ikke bruker bikubisk prøvetaking som den skal. Jeg får bare vente til de faktisk legger det til.

REDIGER3:

Originalbilde

Svar #1

Så hva gjør du hvis alle nettleserne (faktisk, Chrome 5 ga meg ganske god en) ikke gir deg god nok resamplekvalitet? Du implementerer dem selv da! Å kom igjen, vi går inn i den nye æraen av Web 3.0, HTML5-kompatible nettlesere, superoptimaliserte JIT JavaScript-kompilatorer, flerkjernede(†) maskiner, med masse minne, hva er du redd for? Hei, det er ordet java i javascript, så det burde garantere ytelsen, ikke sant? Se, koden for å generere miniatyrbilder:

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

...med hvilke du kan produsere resultater som disse!

så uansett, her er en 'fikset' versjon av ditt eksempel:

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

Nå er det på tide å sette de beste nettleserne dine opp mot hverandre og se hvilken som minst sannsynlig vil øke kundens blodtrykk!

Øh, hvor er sarkasme-taggen min?

(siden mange deler av koden er basert på Anrieff Gallery Generator, er det også dekket av GPL2? Jeg vet ikke)

† faktisk på grunn av begrensning i javascript, støttes ikke fler-kjerne.

Svar #2

Rask bildeendrings-/resample-algoritme som bruker Hermite-filter med JavaScript. Støtter gjennomsiktighet, gir god kvalitet. Forhåndsvisning:

Oppdatering: versjon 2.0 lagt til på GitHub (raskere, webarbeidere + overførbare objekter). Endelig fikk jeg det til å fungere!

Git: https://github.com/viliusle/Hermite-resize
Demo: 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);
}

Svar #3

Prøv pica - det er en høyoptimizert størrelseendrer med valgfrie algoritmer. Se demo.

For eksempel blir det originale bildet fra første innlegg endret i størrelse på 120 ms med Lanczos-filter og 3 px vindu eller 60 ms med Box-filter og 0,5 px vindu. For et stort 17 MB bilde på 5000x3000 px tar endring av størrelse omtrent 1 s på stasjonær og 3 s på mobil.

Alle prinsipper for endring av størrelse ble beskrevet veldig godt i denne tråden, og pica legger ikke til rakettforskning. Men det er optimalisert veldig godt for moderne JIT-er, og er klar til bruk rett ut av boksen (via npm eller bower). I tillegg bruker det webarbeidere når tilgjengelig for å unngå at grensesnittet fryser.

Jeg planlegger også å legge til støtte for uskarp maske snart, fordi det er veldig nyttig etter nedskalering.

Svar #4

Jeg vet at dette er en gammel tråd, men det kan være nyttig for noen mennesker som meg selv som måneder senere opplever dette problemet for første gang.

Her er litt kode som endrer størrelsen på bildet hver gang du laster inn bildet på nytt. Jeg er klar over at dette ikke er optimalt i det hele tatt, men jeg gir det som et bevis på konseptet.

Også, beklager for å bruke jQuery for enkle selektorer, men jeg føler meg bare for komfortabel med syntaksen.

$(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>

Min createImage-funksjon kalles én gang når dokumentet lastes, og etter det kalles den hver gang vinduet mottar en endringshendelse.

Jeg testet det i Chrome 6 og Firefox 3.6, begge på Mac. Denne "teknikken" spiser prosessoren som om det var iskrem om sommeren, men det gjør jobben.

Svar #5

Jeg har lagt ut noen algoritmer for å gjøre bildeinterpolasjon på HTML-canvas-pikselmatriser som kan være nyttige her:

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

Disse kan kopieres/innsettes og kan brukes inne i webarbeidere for å endre størrelse på bilder (eller enhver annen operasjon som krever interpolering - jeg bruker dem til å korrigere fisheye-bilder for øyeblikket).

Jeg har ikke lagt til lanczos-greiene ovenfor, så føl deg fri til å legge det til som en sammenligning hvis du vil.

Relaterte artikler