Can we get height and width of image using sharp? - node.js

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

Related

How do i load google fonts on the sever side node js

I am using node js and canvas to create an API that writes text on a certain image. I successfully created a route such as -> /:sometext/:fontsize/:color/:position-x/:position-y and sends a static image with the text of the given font-size, color, and position on the canvas.
What I am unable to achieve is that I want to send the font family in the route and have that rendered on the screen. Plus, isn't there any other way that I can load at least google fonts without having to download the .ttf file.
What I have tried:
GetGoogleFonts npm package (which was a bad idea, since it was stuck at the installation)
WebFontLoader (Gives "Window is not defined" error)
Steps to Reproduce
Currently, I am using a ttf file to load the font
router.get('/thumbnail/:name/:fontsize/:color/:posx/:posy', function (req, res, next) {
let name = req.params.name;
let fontsize = req.params.fontsize
let positionx = req.params.posx
let positiony = req.params.posy
let color = req.params.color
let myimage = 'static/image1.jpg'
const canvas = createCanvas(2000, 1000)
const ctx = canvas.getContext('2d')
var str = "hi "+name
registerFont('AvenirNext.ttf', { family: 'Avenir Next' })
loadImage(myimage).then((image) => {
ctx.drawImage(image, 0 , 0, 2000, 1000);
ctx.font = fontsize + "px Avenir Next"
ctx.fillStyle = color
ctx.fillText(str, positionx, positiony);
const out = fs.createWriteStream(__dirname + '/test.jpeg')
const stream = canvas.createJPEGStream()
stream.pipe(res)
out.on('finish', () => console.log('The JPEG file was created.'))
})
});
If you don't want to host the ttf files on your own server you could try to use the Google Font Github repo.
// missing imports
const http = require('http');
const fs = require('fs');
const fontFamily = req.params.fontfamily; // example: ArchivoNarrow
// download font from github
const file = fs.createWriteStream(fontFamily + '.ttf');
const request = http.get('https://github.com/google/fonts/blob/master/ofl/' + fontFamily.toLowerCase() + '/' + fontFamily + '-Regular.ttf?raw=true', function(response) {
response.pipe(file);
});
registerFont(fontFamily + '.ttf', { family: fontFamily });
// delete font after the image is created
try {
fs.unlinkSync(fontFamily + '.ttf');
} catch(err) {
console.error(err);
}
Font I used for this example: ArchivoNarrow
From the docs for registerFont
To use a font file that is not installed as a system font, use registerFont() to register the font with Canvas. This must be done before the Canvas is created.
emphasis not mine
You are calling it after you created your canvas.
// first register the font
registerFont('AvenirNext.ttf', { family: 'Avenir Next' });
// then create the canvas
const canvas = createCanvas(2000, 1000);
const ctx = canvas.getContext('2d');
var str = "hi " + name;
But note that this will try to load a font that would be available on your server. If you want to make it target a font on ://fonts.googleapis.com server, you'd need to pass the full URI to the font file (not for the .css).
Also, you should wrap your family name inside quotes (") since it contains a space:
ctx.font = fontsize + 'px "Avenir Next"';

Base64 image resizer nodejs

I am working on a node+express backend.
I am receiving base64 images string from API and I am storing it into MongoDB. Before storing I want to reduce the image size (scaling). I tried JIMP but it is not reading base64 input.
Can anyone please suggest any good node module which accepts base64 image, scale it and return a new base64 string.
Thanks
A little late but I just implemented this feature using the sharp library, so might as well share it now.
I created a function that takes a base64 and the height and width (optional) you want to resize the image to and returns the resized base64 string.
I can just call this function whenever I want.
Code
// Don't forget to import sharp
import sharp from "sharp"
export const resizeBase64 = async ({ base64Image, maxHeight = 640, maxWidth = 640 }) => {
const destructImage = base64Image.split(";");
const mimType = destructImage[0].split(":")[1];
const imageData = destructImage[1].split(",")[1];
try {
let resizedImage = Buffer.from(imageData, "base64")
resizedImage = await sharp(resizedImage).resize(maxHeight, maxWidth).toBuffer()
return `data:${mimType};base64,${resizedImage.toString("base64")}`
} catch (error) {
throwError({ error })
}
};
You can still use JIMP, but all you need to do is to drop the prefix before reading the base64.
Depend on your image format use a specific jpg or png in the following example.
Example:
Jimp.read(Buffer.from((yourImageBuffer).replace(/^data:image\/png;base64,/, ""), 'base64'), function (err, image) {
// Do you stuff here with the image
})
....some logic....
let max_width = 371;
let max_height = 280;
var image = new Image();
image.src = YOUR_BASE64_STRING;
let image_width = image.width;
let image_height = image.height;
let ratio = 1;
if(image_width > max_length || image_height > max_length){ //need to scale
ratio = max_length / image_width;
if(image_height > image_width){
ratio = max_length / image_height;
}
}
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = image_width * ratio; // target width
canvas.height = image_height * ratio; // target height
ctx.drawImage(image,
0, 0, image.width, image.height,
0, 0, canvas.width, canvas.height
);
var RESIZED_BASE64_STRING = canvas.toDataURL();
....some logic....

select a region of desktop screen with Electron

I'm trying to write an application that allow the user to select a region of the screen (like selecting to take a screen shot).
Is that even possible?
To specifically take a full screen shot, use the following code (example pulled from Electron Demo App). You can build off of this example, and use the screen, desktopCapturer and rectangle modules in the electron api to customize the code to get a specific screen/display, or select a specific bounding box (x/y coordinates and pixel area).
const electron = require('electron')
const desktopCapturer = electron.desktopCapturer
const electronScreen = electron.screen
const shell = electron.shell
const fs = require('fs')
const os = require('os')
const path = require('path')
const screenshot = document.getElementById('screen-shot')
const screenshotMsg = document.getElementById('screenshot-path')
screenshot.addEventListener('click', function (event) {
screenshotMsg.textContent = 'Gathering screens...'
const thumbSize = determineScreenShotSize()
let options = { types: ['screen'], thumbnailSize: thumbSize }
desktopCapturer.getSources(options, function (error, sources) {
if (error) return console.log(error)
sources.forEach(function (source) {
if (source.name === 'Entire screen' || source.name === 'Screen 1') {
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPng(), function (error) {
if (error) return console.log(error)
shell.openExternal('file://' + screenshotPath)
const message = `Saved screenshot to: ${screenshotPath}`
screenshotMsg.textContent = message
})
}
})
})
})
function determineScreenShotSize () {
const screenSize = electronScreen.getPrimaryDisplay().workAreaSize
const maxDimension = Math.max(screenSize.width, screenSize.height)
return {
width: maxDimension * window.devicePixelRatio,
height: maxDimension * window.devicePixelRatio
}
}
Other ways you could go about this are:
Use object.getClientRects() in the DOM to specify specific elements you want to capture, although this would require foreknowledge of what they are.
Add event listeners in your view to 'draw' the shape of what you want with mouseClick, mouseMove, etc. This stack overflow question has answers which could be adapted to fit what you want to do.
I doubt you are still looking for a solution to this, but after digging i have found a way to do it using a combination of shelljs and clipboard.
const userDataPath = (app).getPath(
'userData'
)
const useP = path.join(userDataPath, 'uploads')
let randomTmpfile = uniqueFilename(useP, 'prefix')
shelljs.exec(`screencapture -ic ${randomTmpfile}.png`, function (res) {
const image = clipboard.readImage('png').toDataURL()
})

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.

Get file size of GIFs with node-canvas?

After much struggling, I've managed to get node-canvas installed on Windows.
When I try to read in the image size of a GIF, however, it just gives me back 0 for the width and height.
var FileSystem = require('fs');
var Canvas = require('canvas');
FileSystem.readdir(baseDir, function(err, files) {
files.forEach(function(filename) {
var path = Path.join(baseDir, filename);
FileSystem.readFile(path, function(err, buf) {
var img = new Canvas.Image;
img.src = buf;
console.log(path, img.width, img.height);
});
});
Is it not supposed to be able to read GIFs?
You must install giflib and reinstall node-canvas like is said in here https://github.com/LearnBoost/node-canvas/wiki/Installation---OSX and then you will be able to manipulate your gif file (retrieve width/height). But beware, the image processed with canvas will stop being animated.

Resources