原生 nodeJS 调整缓冲区图像大小

NATIVE nodeJS resize buffer image

我在 NODEjs 中有一个缓冲区形式的图像,我想调整它的大小。

理论上这应该可以在 nodeJS 中完成,因为我可以访问包含所有像素数据的缓冲区。

我找了很多地方找到一种使用 NATIVE(仅!)nodejs 调整图像大小的简单方法,没有外部库,但我只找到了使用库的解决方案: https://www.npmjs.com/package/gm

Node gm - resize image and preserve aspect ratio?

Node.js: image resizing without ImageMagick

How to resize an image in Node.js?

How to resize image in Node js

Resize image in node js

Resizing images with Nodejs and Imagemagick

How to resize images on node.js

Resize and crop image and keeping aspect ratio NodeJS & gm

How to resize image size in nodejs using multer

try to resize the stream image with sharp Node.js

Node.js: image resizing without ImageMagick

resize an image without uploading it to anywhere using gm in nodejs

Resize an image in Node.js using jimp and get the path the new image

但是所有这些解决方案都使用某种库,但我只想使用普通的 NodeJS。

我可以用 Buffer 读取像素,所以我应该能够编写一个调整大小的 Buffer,就像这个 C++ 线程 http://www.cplusplus.com/forum/general/2615/ 和许多其他线程一样,它简单地遍历像素并调整它的大小..

我发现了这个问题 Resizing an image in an HTML5 canvas 它使用纯客户端 JavaScript 实现了图像大小调整,而不依赖于 canvas drawImage 来调整它的大小(仅获取图像数据) ,这是他使用的代码:

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

然后是一张图片(由 var img = new Image(); img.src = "something" 制作):

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

所以首先,这在客户端非常慢,但在服务器端可能会更快。节点中需要替换/不存在的东西是 ctx.getImageData(可以用缓冲区复制)

有谁知道在 nodejs 中从哪里开始,这在实际性能方面是否明智?如果不能,可以使用上面提到的 C++ 教程的代码,使用纯 node-gyp 来提高性能吗? (以下内容):

#include<iostream>

class RawBitMap
{
public:
    RawBitMap():_data(NULL), _width(0),_height(0)
    {};

    bool Initialise()
    {
        // Set a basic 2 by 2 bitmap for testing.
        //
        if(_data != NULL)
            delete[] _data;

        _width = 2;
        _height = 2;    
        _data = new unsigned char[ GetByteCount() ];

        //
        _data[0] = 0;   // Pixels(0,0) red value
        _data[1] = 1;   // Pixels(0,0) green value
        _data[2] = 2;   // Pixels(0,0) blue value
        _data[3] = 253; // Pixels(1,0)
        _data[4] = 254;
        _data[5] = 255;
        _data[6] = 253; // Pixels(0,1)
        _data[7] = 254;
        _data[8] = 255;
        _data[9] = 0;   // Pixels(1,1)
        _data[10] = 1;
        _data[11] = 2;  

        return true;
    }

    // Perform a basic 'pixel' enlarging resample.
    bool Resample(int newWidth, int newHeight)
    {
        if(_data == NULL) return false;
        //
        // Get a new buuffer to interpolate into
        unsigned char* newData = new unsigned char [newWidth * newHeight * 3];

        double scaleWidth =  (double)newWidth / (double)_width;
        double scaleHeight = (double)newHeight / (double)_height;

        for(int cy = 0; cy < newHeight; cy++)
        {
            for(int cx = 0; cx < newWidth; cx++)
            {
                int pixel = (cy * (newWidth *3)) + (cx*3);
                int nearestMatch =  (((int)(cy / scaleHeight) * (_width *3)) + ((int)(cx / scaleWidth) *3) );

                newData[pixel    ] =  _data[nearestMatch    ];
                newData[pixel + 1] =  _data[nearestMatch + 1];
                newData[pixel + 2] =  _data[nearestMatch + 2];
            }
        }

        //
        delete[] _data;
        _data = newData;
        _width = newWidth;
        _height = newHeight; 

        return true;
    }

    // Show the values of the Bitmap for demo.
    void ShowData()
    {
        std::cout << "Bitmap data:" << std::endl;
        std::cout << "============" << std::endl;
        std::cout << "Width:  " << _width  << std::endl;
        std::cout << "Height: " << _height  << std::endl;
        std::cout << "Data:" << std::endl;

        for(int cy = 0; cy < _height; cy++)
        {
            for(int cx = 0; cx < _width; cx++)
            {
                int pixel = (cy * (_width *3)) + (cx*3);
                std::cout << "rgb(" << (int)_data[pixel] << "," << (int)_data[pixel+1] << "," << (int)_data[pixel+2] << ") ";
            }
            std::cout << std::endl;
        }
        std::cout << "_________________________________________________________" << std::endl;
    }


    // Return the total number of bytes in the Bitmap.
    inline int GetByteCount()
    {
        return (_width * _height * 3);
    }

private:
    int _width;
    int _height;
    unsigned char* _data;

};


int main(int argc, char* argv[])
{
    RawBitMap bitMap;

    bitMap.Initialise();
    bitMap.ShowData();

    if (!bitMap.Resample(4,4))
        std::cout << "Failed to resample bitMap:" << std::endl ; 
    bitMap.ShowData();

    bitMap.Initialise();
    if (!bitMap.Resample(3,3))
        std::cout << "Failed to resample bitMap:" << std::endl ;
    bitMap.ShowData();


    return 0;
}

我想这是在创建一个 2x2 位图并调整它的大小,但基本原理应该仍然可以应用于纯 node-gyp。还有其他人这样做吗?这到底实用吗?

找到了一个简单、快速的方法,只使用节点的 pngjs 库(它是用纯原生节点编写的,所以即使是可以优化),以及内置的流库。因此,在代码的顶部只需执行 var PNG = require("pngjs").PNG, stream = require("stream"); 然后使用以下代码:

function cobRes(iBuf, width, cb) {
        b2s(iBuf)
        .pipe(new PNG({
            filterType: -1
        }))
        .on('parsed', function() {

            var nw = width;
            var nh = nw *  this.height /this.width;
            var f = resize(this, nw, nh);

            sbuff(f.pack(), b=>{
                cb(b);
            })
        })


        function resize(srcPng, width, height) {
            var rez = new PNG({
                width:width,
                height:height
            });
            for(var i = 0; i < width; i++) {
                var tx = i / width,
                    ssx = Math.floor(tx * srcPng.width);
                for(var j = 0; j < height; j++) {
                    var ty = j / height,
                        ssy = Math.floor(ty * srcPng.height);
                    var indexO = (ssx + srcPng.width * ssy) * 4,
                        indexC = (i + width * j) * 4,
                        rgbaO = [
                            srcPng.data[indexO  ],
                            srcPng.data[indexO+1],
                            srcPng.data[indexO+2],
                            srcPng.data[indexO+3]
                        ]
                    rez.data[indexC  ] = rgbaO[0];
                    rez.data[indexC+1] = rgbaO[1];
                    rez.data[indexC+2] = rgbaO[2];
                    rez.data[indexC+3] = rgbaO[3];
                }
            }
            return rez;
        }

        function b2s(b) {
            var str = new stream.Readable();
            str.push(b);
            str.push(null);
            return str;
        }
        function sbuff(stream, cb) {
            var bufs = []
            var pk = stream;
            pk.on('data', (d)=> {
                bufs.push(d);

            })
            pk.on('end', () => {
                var buff = Buffer.concat(bufs);
                cb(buff);
            });
        }
    }

然后使用:

cobRes(fs.readFileSync("somePNGfile.png"), 200, buffer => fs.writeFileSync("new.png", buffer))

不确定为什么每个人都为此使用复杂的库:)