NodeJS: How to use image-size with base64 image? - node.js

I found the node module image-size and want to use it to get the dimensions of a base64 encoded image. The tutorial gives the following example for getting the dimensions:
var sizeOf = require('image-size');
var dimensions = sizeOf('images/funny-cats.png');
console.log(dimensions.width, dimensions.height);
An here in the comment of the second answer someone wrote it's working for base64 images as well. So I tried the follwing:
var img = Buffer.from(base64, 'base64');
var dimensions = sizeOf(img);
console.log(dimensions.width, dimensions.height);
But I get a TypeError: unsupported file type: undefined (file: undefined)
How can I use sizeOf-Method of the image-size package with a base64 string I have in a variable?

Try this
var img = Buffer.from(base64.substr(23), 'base64');
var dimensions = sizeOf(img);
console.log(dimensions.width, dimensions.height);
substr(23) cuts off data:image/jpeg;base64,, which is necessary to properly create a Buffer from your base64 data.

Here's another solution using puppeteer worth considering
const puppeteer = require('puppeteer')
// image data
const data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBg...'
const browser = await puppeteer.launch()
const page = await browser.newPage();
const dimensions = await page.evaluate(data => new Promise(resolve => {
// client-like
const img = new Image()
img.onload = () => resolve({ width: img.width, height: img.height })
img.src = data
}), data)
console.log(dimensions.width, dimensions.height)
browser.close()
Use this if the code is run in a wsl context :
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']
})
NOTE : Of course this method is really slow (because it opens a Chromium instance in the background, loads a webpage and its scripts, waits for rendering, etc...). I am providing this solution just for reference as in some cases being able to execute a script just like in a normal browser can be really useful.

Related

tf.image.cropAndResize throwing "method must be bilinear or nearest, but was undefined" error

I am trying to run an image categorization model in firebase cloud functions using tensorflow.js (specifically tfjs-node) but am running into the flowing error:
Error: method must be bilinear or nearest, but was undefined
at assert (/workspace/node_modules/#tensorflow/tfjs-core/dist/tf-core.node.js:698:15)
at cropAndResize_ (/workspace/node_modules/#tensorflow/tfjs-core/dist/tf-core.node.js:21340:5)
at Object.cropAndResize__op [as cropAndResize] (/workspace/node_modules/#tensorflow/tfjs-core/dist/tf-core.node.js:4287:29)
at prepImage (/workspace/handlers/models.js:58:35)
at /workspace/handlers/models.js:68:44
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async exports.isFurnished (/workspace/handlers/models.js:10:17)
at async exports.getanalysis (/workspace/handlers/apis.js:103:16)
The error is being thrown by the tf.image.cropAndResize() function. What is strange about this error is that cropAndResize() should be automatically using its default value of "bilinear" as specified in the docs.
Stranger yet, when I run it locally I don't get any errors. My local machine is running node v12.16.0.
Below is my code. please note that I am only lading signature.json from the firebase storage and fetching /standardizing an image (I am not loading and running the actual ts model).
const { admin, db } = require("../util/admin");
const firebase = require("firebase");
const tf = require("#tensorflow/tfjs-node");
const fetch = require("node-fetch");
exports.isFurnished = async (imgUrl) => {
const sigPath = "models/signature.json";
const signature = await loadSignature(sigPath);
const image = await loadImage(imgUrl, signature);
return "It worked!";
};
//signature---------------------------
const loadSignature = (filePath) => {
let file = admin.storage().bucket().file(filePath);
return file
.download()
.then((res) => JSON.parse(res[0].toString("utf8")))
.catch((err) => err.message);
};
//Image-------------------------------
const loadImage = (imgUrl, signature) => {
return fetchImage(imgUrl).then((image) => prepImage(image, signature));
};
const fetchImage = async (url) => {
const response = await fetch(url);
const buffer = await response.buffer();
return buffer;
};
const prepImage = (rawImage, signature) => {
const image = tf.node.decodeImage(rawImage, 3);
const [height, width] = signature.inputs.Image.shape.slice(1, 3);
const [imgHeight, imgWidth] = image.shape.slice(0, 2);
const normalizedImage = tf.div(image, tf.scalar(255));
const reshapedImage = normalizedImage.reshape([1, ...normalizedImage.shape]);
let top = 0;
let left = 0;
let bottom = 1;
let right = 1;
if (imgHeight != imgWidth) {
const size = Math.min(imgHeight, imgWidth);
left = (imgWidth - size) / 2 / imgWidth;
top = (imgHeight - size) / 2 / imgHeight;
right = (imgWidth + size) / 2 / imgWidth;
bottom = (imgHeight + size) / 2 / imgHeight;
}
return tf.image.cropAndResize(
reshapedImage,
[[top, left, bottom, right]],
[0],
[height, width]
);
};
Have I made an error that I'm just not seeing or is this a node and/or tsjs issue?
Also, adding in the "bilinear" parameter yields this error:
Error: Invalid napi_status: A number was expected
As commented above, TensorFlow.js version 2.8.0 seems to have introduced some breaking changes. Workaround (at the time of writing) is to keep using version 2.7.0.
I am working with bodypix. It was working fine until this morning. Although I haven't changed anything, since this afternoon this exact error came up. It could be Tensorflow's issue. Or,
I checked on Windows 8.1. There, it works totally fine. The problem happens on windows 10.
EDIT: I am quite sure it's from TensorFlow. Not the windows. I was using CDN to get the bodypix and after updating the cdn address the error disappeared.
previous: https://cdn.jsdelivr.net/npm/#tensorflow-models/body-pix/dist/body-pix.min.js
https://cdn.jsdelivr.net/npm/#tensorflow/tfjs/dist/tf.min.js
Now: https://cdn.jsdelivr.net/npm/#tensorflow-models/body-pix#2.0.5/dist/body-pix.min.js
https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#2.7.0/dist/tf.min.js

Feeding PDF generated from pdfkit as input to pdf-lib for merging

I am trying to send a pdfkit generated pdf file as input to pdflib for merging. I am using async function. My project is being developed using sails Js version:"^1.2.3", "node": "^12.16", my pdf-kit version is: "^0.11.0", "pdf-lib": "^1.9.0",
This is the code:
const textbytes=fs.readFileSync(textfile);
var bytes1 = new Uint8Array(textbytes);
const textdoc = await PDFDocumentFactory.load(bytes1)
The error i am getting is:
UnhandledPromiseRejectionWarning: Error: Failed to parse PDF document (line:0 col:0 offset=0): No PDF header found
Please help me with this issue.
You really don't need this line.
var bytes1 = new Uint8Array(textbytes);
By just reading the file and sending textbytes in the parameters is more than enough.
I use this function to merge an array of pdfBytes to make one big PDF file:
async function mergePdfs(pdfsToMerge)
{
const mergedPdf = await pdf.PDFDocument.create();
for (const pdfCopyDoc of pdfsToMerge)
{
const pdfDoc = await pdf.PDFDocument.load(pdfCopyDoc);
const copiedPages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
copiedPages.forEach((page) => {
mergedPdf.addPage(page);
});
}
const mergedPdfFile = await mergedPdf.save();
return mergedPdfFile;
};
So basically after you add the function mergePdfs(pdfsToMerge)
You can just use it like this:
const textbytes = fs.readFileSync(textfile);
const textdoc = await PDFDocumentFactory.load(bytes1)
let finalPdf = await mergePdfs(textdoc);

Can we get height and width of image using sharp?

I am using sharp to resize bulk of image. So I am resizing them to 500px by preserving their aspect ratio. Also I want to resize height to 500px and auto resize width if height is greater than with and vice versa. To do that I need to get image, height from Image buffer. I know there are pretty number of packages available to do so. But I was hoping if I can do that using sharp buffer itself.
Yes you can get the width and height of an image with sharp by using the metadata() function :
const image = await sharp(file.buffer)
const metadata = await image.metadata()
console.log(metadata.width, metadata.height)
You can get a lot more information from metadata , here is the documentation : https://sharp.pixelplumbing.com/api-input#metadata
To get the dimensions that are recorded in the header of the input image:
const image = await sharp(file.buffer);
const metadata = await image.metadata();
console.log(metadata.width, metadata.height);
However, operations like image.resize(...) will not affect the .metadata(). To get the dimensions after performing operations on the image, use .toBuffer({ resolveWithObject: true }):
const image = await sharp(file.buffer);
const resizedImage = image.resize(640);
const { info } = await resizedImage.png().toBuffer({ resolveWithObject: true });
console.log(info.width, info.height);
Sharp is very flexible, it has a number of options for resizing images. Using an option of fit: "contain" should accomplish what you wish.
Others are available of course, documented here: https://sharp.pixelplumbing.com/api-resize#resize
You can also specify the background color to fill space within the resized image, I'm using white here.
The code will look something like this:
const fs = require("fs");
const path = require("path");
const sharp = require("sharp");
const inputDir = "./input-images";
const outputDir = "./output-images";
const requiredDimension = 500;
const inputImages = fs.readdirSync(inputDir).map(file => path.join(inputDir, file));
function resizeImage(imagePath) {
sharp(imagePath)
.resize( { width: requiredDimension, height: requiredDimension, fit: "contain", background: { r: 255, g: 255, b: 255, alpha: 1 }})
.toFile(path.join(outputDir, path.basename(imagePath) + "-resized" + path.extname(imagePath)), (err, info) => {
if (err) {
console.error("An error occurred resizing image:", err);
}
});
}
// Ensure output dir exists...
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir)
}
inputImages.forEach(resizeImage);

Can't change leaflet setView params when using leaflet-headless

I need to generate a map on the server side using Nodejs and then create an image of that map. I'm using leaflet-headless to create the map and generate the image.
This is the code:
const L = require('leaflet-headless');
const document = global.document;
let createMap = (lanLat) => {
const element = document.createElement('div');
element.id = 'map-leaflet-image';
document.body.appendChild(element);
const filename = path.join(__dirname, '/leaflet-image.png');
const map = L.map(element.id).setView([0, 0], 3);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
map.saveImage(filename, () => {
console.log('done');
})
};
This works and the image is saved but when I change the setView() parameters to setView([0,0], 1)(zoom out) I receive an error message:
return prev.apply(ctx, arguments);
Error: Image given has not completed loading
at Error (native)
at CanvasRenderingContext2D.ctx.(anonymous function) [as drawImage]
Any thoughts?
If this might interest someone, the problem was in the map.save() function which uses the leaflet-image lib.
This happened due to a weirdly specific scenario where a marker with certain coordinates, when added to the map with any other marker(?!), caused the error. I removed that marker and it worked.

Capture and save image with robotjs

I tried to capture and save an image from screen with robotjs (http://robotjs.io/) but when I open the file bitmap the image is not in a valid format. This is my code:
var robot = require("robotjs");
var fs = require("fs");
var size = 10;
var img = robot.screen.capture(0, 0, size, size);
fs.writeFileSync('img.bmp',img.image);
Jimp supports converting Raw Pixel Buffer into PNG out-of-the-box and works a lot faster.
let robot = require("robotjs");
let Jimp = require('jimp');
const img = robot.screen.capture(0, 0, width, height).image;
new Jimp({data: img, width, height}, (err, image) => {
image.write(fileName);
});
The image will be saved with the wrong colors. To fix it, you can use the following code:
function screenCaptureToFile2(robotScreenPic, path) {
return new Promise((resolve, reject) => {
try {
const image = new Jimp(robotScreenPic.width, robotScreenPic.height);
let pos = 0;
image.scan(0, 0, image.bitmap.width, image.bitmap.height, (x, y, idx) => {
image.bitmap.data[idx + 2] = robotScreenPic.image.readUInt8(pos++);
image.bitmap.data[idx + 1] = robotScreenPic.image.readUInt8(pos++);
image.bitmap.data[idx + 0] = robotScreenPic.image.readUInt8(pos++);
image.bitmap.data[idx + 3] = robotScreenPic.image.readUInt8(pos++);
});
image.write(path, resolve);
} catch (e) {
console.error(e);
reject(e);
}
});
}
var pic = robot.screen.capture();
screenCaptureToFile2(pic)
Note that your img.image Buffer from Robotjs is a raw buffer with pixels; not a BMP or PNG or any other format.
You should do some data conversion and probably save it using a library that supports writing to file (I do not see that in Robotjs).
Please look at this other question, which also uses robot.screen.capture and saves file to a PNG file using Jimp library. That code answers your question too.

Resources