Resizing an image in an HTML5 canvas

后端 未结 18 3115
半阙折子戏
半阙折子戏 2020-11-22 03:37

I\'m trying to create a thumbnail image on the client side using javascript and a canvas element, but when I shrink the image down, it looks terrible. It looks as if it was

18条回答
  •  清歌不尽
    2020-11-22 04:09

    So what do you do if all the browsers (actually, Chrome 5 gave me quite good one) won't give you good enough resampling quality? You implement them yourself then! Oh come on, we're entering the new age of Web 3.0, HTML5 compliant browsers, super optimized JIT javascript compilers, multi-core(†) machines, with tons of memory, what are you afraid of? Hey, there's the word java in javascript, so that should guarantee the performance, right? Behold, the thumbnail generating code:

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

    ...with which you can produce results like these!

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

    so anyway, here is a 'fixed' version of your example:

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

    Now it's time to pit your best browsers out there and see which one will least likely increase your client's blood pressure!

    Umm, where's my sarcasm tag?

    (since many parts of the code is based on Anrieff Gallery Generator is it also covered under GPL2? I dunno)

    actually due to limitation of javascript, multi-core is not supported.

提交回复
热议问题