【问题标题】:How to save binary buffer to png file in nodejs?如何在nodejs中将二进制缓冲区保存到png文件?
【发布时间】:2017-05-09 23:57:36
【问题描述】:

我有包含位图信息的二进制 nodejs 缓冲区对象。如何从缓冲区制作图像并将其保存到文件中?

编辑:

我尝试使用@herchu 所说的文件系统包,但如果我这样做:

let robot = require("robotjs")
let fs = require('fs')

let size = 200
let img = robot.screen.capture(0, 0, size, size)


let path = 'myfile.png'
let buffer = img.image

fs.open(path, 'w', function (err, fd) {
  if (err) {
    // Something wrong creating the file
  }

  fs.write(fd, buffer, 0, buffer.length, null, function (err) {
    // Something wrong writing contents!
  })
})

我明白了

【问题讨论】:

    标签: javascript node.js buffer


    【解决方案1】:

    虽然 @herchu 和 @Jake 的解决方案有效,但它们的速度非常慢(根据我的经验,需要 10 到 15 秒)。


    Jimp supports converting Raw Pixel Buffer into PNG 开箱即用,工作速度更快(亚秒级)。

    const img = robot.screen.capture(0, 0, width, height).image;
    new Jimp({data: img, width, height}, (err, image) => {
        image.write(fileName);
    });
    

    【讨论】:

    • 我有这个错误:没有找到匹配的构造函数重载。请参阅文档了解如何调用 Jimp 构造函数。
    • 这相当于快了 100000e10 倍。
    • 这在 Ubuntu 18.04 上对我来说效果不佳。起初,生成的图像是透明的。我注意到 alpha 通道始终为 0,所以我将其更新为 255 只是为了发现颜色错误。由于某种原因,robotjs 创建的缓冲区使用 bgra 而不是 rgba。使用以下代码为我修复了该问题,并且仍然比此处建议的其他解决方案更快: let fixedImg = new Uint8Array(img.length); for (let i=0; i
    【解决方案2】:

    注意:我正在根据您上次的编辑编辑我的答案

    如果您使用的是 Robotjs,请检查其 Bitmap object 是否包含 原始 像素数据的缓冲区——不是 PNG 或任何其他文件格式内容,只是彼此相邻的像素(正好 200 x 200 个元素在您的情况下)。

    我还没有在 Robotjs 库中找到任何以其他格式编写内容的函数(我也不知道),所以在这个答案中,我使用不同的库 Jimp 进行图像处理。

    let robot = require("robotjs")
    let fs = require('fs')
    let Jimp = require('jimp')
    
    let size = 200
    let rimg = robot.screen.capture(0, 0, size, size)
    let path = 'myfile.png'
    
    // Create a new blank image, same size as Robotjs' one
    let jimg = new Jimp(size, size);
    for (var x=0; x<size; x++) {
            for (var y=0; y<size; y++) {
                    // hex is a string, rrggbb format
                    var hex = rimg.colorAt(x, y);
                    // Jimp expects an Int, with RGBA data,
                    // so add FF as 'full opaque' to RGB color
                    var num = parseInt(hex+"ff", 16)
                    // Set pixel manually
                    jimg.setPixelColor(num, x, y);
            }
        }
    jimg.write(path)
    

    请注意,转换是通过手动迭代所有像素来完成的;这在 JS 中很慢。还有一些关于每个库如何处理其像素格式的详细信息,因此在循环中需要进行一些操作——从嵌入的 cmets 中应该可以清楚地看到。

    【讨论】:

    • 我发现了这个很棒的库。我做了一个简单的颜色机器人,然后我决定做一些更智能的东西,所以我在 nodejs 中设置了模板匹配。我可以为我的屏幕拍照,但库返回一个缓冲区。 robotjs.io/docs/syntax#screencapturex-y-width-height。恐怕我不能使用文件系统。 github.com/octalmage/robotjs/issues/222
    • 我编辑了我的问题。如果您对 C 或其他更难的语言有所了解,我将非常感谢您的帮助,因为我已经在这个问题上花了几个小时。 ://
    • 我已经编辑了这个问题。该代码现在基于您的代码,使用robotjs 并生成一个工作PNG 文件。 (原始问题中没有指定您的缓冲区不是 PNG 文件......而是原始的 robotsjs 缓冲区)
    • 我接受了这个答案,因为你解释了很多。可悲的是,当您看到 600x600 及以上的图片时,速度会变慢。我将使用 shelljs 并生成屏幕截图并在那里归档。然后我可能会用快递服务器为我的客户提供服务。或者只是使用 socket.io 发出它们。
    • 我在下面添加了一个替代代码示例,用于处理运行速度更快的像素(对于大多数实际用途来说应该足够快)。感谢@herchu 为我指明了正确的方向。 :)
    【解决方案3】:

    将此作为附录添加到@herchu 接受的答案中,此代码示例可以更快地处理/转换原始字节(对于我来说,全屏

    var jimg = new Jimp(width, height);
    for (var x=0; x<width; x++) {
        for (var y=0; y<height; y++) {
            var index = (y * rimg.byteWidth) + (x * rimg.bytesPerPixel);
            var r = rimg.image[index];
            var g = rimg.image[index+1];
            var b = rimg.image[index+2];
            var num = (r*256) + (g*256*256) + (b*256*256*256) + 255;
            jimg.setPixelColor(num, x, y);
        }
    }
    

    【讨论】:

      【解决方案4】:

      快四倍! 如果使用此脚本,全屏 1920x1080 大约需要 280ms 和 550Kb。 当我将每个字节的 2 字节线程与前额进行比较时,我发现了这种模式。

      const robotjs = require('robotjs');
      const Jimp = require('jimp');
      const app = require('express').Router();
      
      app.get('/screenCapture', (req, res)=>{
        let image = robotjs.screen.capture();
        for(let i=0; i < image.image.length; i++){
            if(i%4 == 0){
                [image.image[i], image.image[i+2]] = [image.image[i+2], image.image[i]];
            }
        }
      
        var jimg = new Jimp(image.width, image.height);
        jimg.bitmap.data = image.image;
        jimg.getBuffer(Jimp.MIME_PNG, (err, result)=>{
            res.set('Content-Type', Jimp.MIME_PNG);
            res.send(result);
        });
      });
      

      如果您在jimp.getBuffer 之前添加此代码,您将获得大约 210 毫秒和 320Kb 的全屏

        jimg.rgba(true);
        jimg.filterType(1); 
        jimg.deflateLevel(5);
        jimg.deflateStrategy(1);
      

      【讨论】:

        【解决方案5】:

        我建议你看看 sharp,因为它的性能指标优于 jimp。

        robotjs 屏幕捕获的问题实际上恰好是非常有效的,它是 BGRA 颜色模型而不是 RGBA。所以你需要做额外的颜色旋转。

        此外,当我们从桌面截取屏幕截图时,我无法想象我们需要透明度的情况。所以,我建议忽略它。

        const [left, top, width, height] = [0, 0, 100, 100]
        const channels = 3
        const {image, width: cWidth, height: cHeight, bytesPerPixel, byteWidth} = robot.screen.capture(left, right, width, height)
        const uint8array = new Uint8Array(cWidth*cHeight*channels);
        for(let h=0; h<cHeight; h+=1) {
          for(let w=0; w<cWidth; w+=1) {
            let offset = (h*cWidth + w)*channels
            let offset2 = byteWidth*h + w*bytesPerPixel
            uint8array[offset] = image.readUInt8(offset2 + 2)
            uint8array[offset + 1] = image.readUInt8(offset2 + 1)
            uint8array[offset + 2] = image.readUInt8(offset2 + 0)
          }
        }
        await sharp(Buffer.from(uint8array), {
          raw: {
            width: cWidth,
            height: cHeight,
            channels,
          }
        }).toFile('capture.png')

        我在这里使用中间数组,但实际上你可以只交换屏幕截图的结果。

        【讨论】:

          猜你喜欢
          • 2012-10-03
          • 2017-04-12
          • 2011-07-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-04
          • 1970-01-01
          • 2012-01-26
          相关资源
          最近更新 更多