I'm using Robot.js to take screenshots (I don't really want to install any other heavy packages over 1 mb, but I'm already using Robot.js and no, Jimp isn't what I'm looking for). Since the captureScreen
method of Robot.js returns a raw pixel buffer, I'm converting the buffer to a png buffer.
However, I can't figure out why my conversion mixes up the colors of the screenshot. This is my current code:
const fs = require('fs');
const zlib = require('zlib');
const robot = require("@jitsi/robotjs");
const { width, height } = robot.getScreenSize();
const bitDepth = 8;
const colorType = 6;
const bytesPerPixel = 4;
const rawPixelData = robot.captureScreen().image;
const filteredData = Buffer.alloc((width * bytesPerPixel + 1) * height);
for (let i = 0; i < height; i++) {
filteredData[i * (width * bytesPerPixel + 1)] = 0;
rawPixelData.copy(filteredData, i * (width * bytesPerPixel + 1) + 1, i * width * bytesPerPixel, (i + 1) * width * bytesPerPixel);
};
const compressedData = zlib.deflateSync(filteredData);
function crc32(buf) {
let crc = 0xffffffff;
for (let i = 0; i < buf.length; i++) {
crc ^= buf[i];
for (let j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >>> 1) ^ 0xedb88320;
} else {
crc >>>= 1;
}
}
}
return Buffer.from([(crc ^ 0xffffffff) >>> 24, (crc ^ 0xffffffff) >>> 16, (crc ^ 0xffffffff) >>> 8, (crc ^ 0xffffffff) & 0xff]);
}
function createChunk(type, data) {
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length, 0);
const typeBuffer = Buffer.from(type, "ascii");
const crc = crc32(Buffer.concat([typeBuffer, data]));
return Buffer.concat([length, typeBuffer, data, crc]);
}
const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdrData = Buffer.alloc(13);
ihdrData.writeUInt32BE(width, 0);
ihdrData.writeUInt32BE(height, 4);
ihdrData.writeUInt8(bitDepth, 8);
ihdrData.writeUInt8(colorType, 9);
ihdrData.writeUInt8(0, 10);
ihdrData.writeUInt8(0, 11);
ihdrData.writeUInt8(0, 12);
const ihdrChunk = createChunk("IHDR", ihdrData);
const idatChunk = createChunk("IDAT", compressedData);
const iendChunk = createChunk("IEND", Buffer.alloc(0));
const pngData = Buffer.concat([pngSignature, ihdrChunk, idatChunk, iendChunk]);
fs.writeFileSync("./output.png", pngData);
console.log("PNG file written: output.png");
As an example, this is what a screenshot of my VS Code session currently looks like:
Any help would be greatly appreciated! ;)
I'm using Robot.js to take screenshots (I don't really want to install any other heavy packages over 1 mb, but I'm already using Robot.js and no, Jimp isn't what I'm looking for). Since the captureScreen
method of Robot.js returns a raw pixel buffer, I'm converting the buffer to a png buffer.
However, I can't figure out why my conversion mixes up the colors of the screenshot. This is my current code:
const fs = require('fs');
const zlib = require('zlib');
const robot = require("@jitsi/robotjs");
const { width, height } = robot.getScreenSize();
const bitDepth = 8;
const colorType = 6;
const bytesPerPixel = 4;
const rawPixelData = robot.captureScreen().image;
const filteredData = Buffer.alloc((width * bytesPerPixel + 1) * height);
for (let i = 0; i < height; i++) {
filteredData[i * (width * bytesPerPixel + 1)] = 0;
rawPixelData.copy(filteredData, i * (width * bytesPerPixel + 1) + 1, i * width * bytesPerPixel, (i + 1) * width * bytesPerPixel);
};
const compressedData = zlib.deflateSync(filteredData);
function crc32(buf) {
let crc = 0xffffffff;
for (let i = 0; i < buf.length; i++) {
crc ^= buf[i];
for (let j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >>> 1) ^ 0xedb88320;
} else {
crc >>>= 1;
}
}
}
return Buffer.from([(crc ^ 0xffffffff) >>> 24, (crc ^ 0xffffffff) >>> 16, (crc ^ 0xffffffff) >>> 8, (crc ^ 0xffffffff) & 0xff]);
}
function createChunk(type, data) {
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length, 0);
const typeBuffer = Buffer.from(type, "ascii");
const crc = crc32(Buffer.concat([typeBuffer, data]));
return Buffer.concat([length, typeBuffer, data, crc]);
}
const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdrData = Buffer.alloc(13);
ihdrData.writeUInt32BE(width, 0);
ihdrData.writeUInt32BE(height, 4);
ihdrData.writeUInt8(bitDepth, 8);
ihdrData.writeUInt8(colorType, 9);
ihdrData.writeUInt8(0, 10);
ihdrData.writeUInt8(0, 11);
ihdrData.writeUInt8(0, 12);
const ihdrChunk = createChunk("IHDR", ihdrData);
const idatChunk = createChunk("IDAT", compressedData);
const iendChunk = createChunk("IEND", Buffer.alloc(0));
const pngData = Buffer.concat([pngSignature, ihdrChunk, idatChunk, iendChunk]);
fs.writeFileSync("./output.png", pngData);
console.log("PNG file written: output.png");
As an example, this is what a screenshot of my VS Code session currently looks like:
Any help would be greatly appreciated! ;)
Share Improve this question asked Feb 7 at 14:24 Dinoscape DinoscapeDinoscape Dinoscape 541 silver badge3 bronze badges 2- " If anybody is having the same problem, here's the solution:" - self-answers are welcome on SO, consider posting it. FWIW this is the reason why a screenshot of color swatch would be helpful in such case – Estus Flask Commented Feb 7 at 14:26
- @EstusFlask Yeah, imma do that. Thanks anyways! – Dinoscape Dinoscape Commented Feb 7 at 14:54
1 Answer
Reset to default 1For future reference, Robotjs uses BGRA instead of RGBA. This correctly rearranges the colors:
rawPixelData[idx] (Blue) → filteredData[fi + 2]
rawPixelData[idx + 1] (Green) → filteredData[fi + 1]
rawPixelData[idx + 2] (Red) → filteredData[fi]
rawPixelData[idx + 3] (Alpha) remains the same
So the updated code would look like this:
const fs = require('fs');
const zlib = require('zlib');
const robot = require("@jitsi/robotjs");
const { width, height } = robot.getScreenSize();
const bitDepth = 8;
const colorType = 6;
const bytesPerPixel = 4;
const rawPixelData = robot.captureScreen().image;
const filteredData = Buffer.alloc((width * bytesPerPixel + 1) * height);
for (let i = 0; i < height; i++) {
filteredData[i * (width * bytesPerPixel + 1)] = 0; // PNG scanline filter type 0
for (let j = 0; j < width; j++) {
const idx = (i * width + j) * bytesPerPixel;
const fi = i * (width * bytesPerPixel + 1) + 1 + j * bytesPerPixel;
// Convert BGRA to RGBA
filteredData[fi] = rawPixelData[idx + 2]; // Red
filteredData[fi + 1] = rawPixelData[idx + 1]; // Green
filteredData[fi + 2] = rawPixelData[idx]; // Blue
filteredData[fi + 3] = rawPixelData[idx + 3]; // Alpha
};
};
const compressedData = zlib.deflateSync(filteredData);
function crc32(buf) {
let crc = 0xffffffff;
for (let i = 0; i < buf.length; i++) {
crc ^= buf[i];
for (let j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >>> 1) ^ 0xedb88320;
} else {
crc >>>= 1;
}
}
}
return Buffer.from([(crc ^ 0xffffffff) >>> 24, (crc ^ 0xffffffff) >>> 16, (crc ^ 0xffffffff) >>> 8, (crc ^ 0xffffffff) & 0xff]);
}
function createChunk(type, data) {
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length, 0);
const typeBuffer = Buffer.from(type, "ascii");
const crc = crc32(Buffer.concat([typeBuffer, data]));
return Buffer.concat([length, typeBuffer, data, crc]);
}
const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdrData = Buffer.alloc(13);
ihdrData.writeUInt32BE(width, 0);
ihdrData.writeUInt32BE(height, 4);
ihdrData.writeUInt8(bitDepth, 8);
ihdrData.writeUInt8(colorType, 9);
ihdrData.writeUInt8(0, 10);
ihdrData.writeUInt8(0, 11);
ihdrData.writeUInt8(0, 12);
const ihdrChunk = createChunk("IHDR", ihdrData);
const idatChunk = createChunk("IDAT", compressedData);
const iendChunk = createChunk("IEND", Buffer.alloc(0));
const pngData = Buffer.concat([pngSignature, ihdrChunk, idatChunk, iendChunk]);
fs.writeFileSync("./output.png", pngData);
console.log("PNG file written: output.png");
I hope this helps! ;)