how to pass image buffer data to gm in() GraphicsMagic - node.js

var buf = require('fs').readFileSync('test.jpg');
gm().in('-page', '+0+0').in(buf,'test.jpg').write('output.jpg', function (err) {
if (err) console.log(err);
})
in this case i want to pass buffer data as input to the gm.in() method.
Below is the link I'm referencing but in it, an image path is used as an input. I want to use buffer data as an input. How can I do this?
Tile four images together using Node.js and GraphicsMagick

Without modifying the source of GraphicsMagick itself, you can't. The gm module interacts with the GraphicsMagick program through the command line. The arguments you're passing through the .in() method are being converted into command-line arguments. The GraphicsMagick program only accepts filenames for this argument and will not attempt to process any direct form of data.
If you really needed to make this work without the filesystem, you could always download the GraphicsMagick source code and change the CLI to accept some form of data blob instead of a URL for this argument.

Actually I was creating a poster with two different image one is template image "background" and 2nd is top image with some text. I tried with gm but i loose image quality. Someone guide me to use Buffer data as input to improve the image quality. I tried but don't know how to pass Buffer data as an input.
So finally i decided to use node child process with command string. Here is the sample code i am sharing with you.
var fs = require('fs');
var gm = require("gm");
var exec = require('child_process').exec;
var IMAGEFILEPATH = "/images";
var gmcreateImage = function() {
var imageConfig = {"topimage":{"density":"300x300","startx":925,"starty":650,"width":575,"height":825},
"offers": [
{"startx": 75, "starty": 850, "msg": "SAVE 5$", "textcolor": "#4f61ac", "font": "Arial Bold", "fontsize":34,"stroke":{"color":"#4f61ac","width":4}},
{"startx": 75, "starty": 970, "msg": "per gallon", "textcolor": "#4f61ac", "font": "Arial Bold", "fontsize":34,"stroke":{"color":"#4f61ac","width":4}},
{"startx": 75, "starty": 1150, "msg": "With the purchase of", "textcolor": "black", "font": "Arial", "fontsize":18,"stroke":{"color":"black","width":1}},
{"startx": 75, "starty": 1260, "msg": "any Pepsi Z0 S2", "textcolor": "black", "font": "Arial", "fontsize":16,"stroke":{"color":"black","width":1}},
{"startx": 75, "starty": 1370, "msg": "on all flavours", "textcolor": "black", "font": "Arial", "fontsize":16,"stroke":{"color":"black","width":1}},
{"startx": 75, "starty": 1480, "msg": "Ask for details.", "textcolor": "black", "font": "Arial", "fontsize":18,"stroke":{"color":"black","width":1}}
]};
var addLast=imageConfig.topimage.last;
var commandStr = "gm convert '-page' '+0+0' '-units' 'PixelsPerInch' '-density' '" + imageConfig.topimage.density + "' '" + IMAGEFILEPATH+ "/template.jpg' ";
var imageActualPosition={};
imageActualPosition["x"] = imageConfig.topimage.startx;
imageActualPosition["y"] = imageConfig.topimage.starty;
if (!addLast) {
commandStr += " '-page' '+" + imageActualPosition["x"] + "+" + imageActualPosition["y"] + "' '" + IMAGEFILEPATH + "/top.jpg' ";
}
var offers = imageConfig.offers;
for (var i in offers) {
var color = offers[i].textcolor;
var startX = offers[i].startx;
var startY = offers[i].starty;
var font = offers[i].font;
var fontSize = offers[i].fontsize;
var msg = offers[i].msg;
var offerStr = "";
if (offers[i].stroke) {
offerStr += " '-stroke' '" + offers[i].stroke.color + "' '-strokewidth' '" + offers[i].stroke.width + "'";
}
offerStr += " '-fill' '" + color + "' '-pointsize' '" + fontSize + "' '-draw' 'text " + startX + " " + startY + " \"" + msg + "\"'";
commandStr += offerStr;
}
if (addLast) {
commandStr += " '-page' '+" + imageActualPosition["x"] + "+" + imageActualPosition["y"] + "' '" + IMAGEFILEPATH + "/top.jpg' ";
}
var finalImage="done.jpg";
commandStr += " '-mosaic' '-quality' '100' '" + IMAGEFILEPATH + finalImage + "'";
exec(commandStr, function(err, stdout, stderr) {
if (err) {
console.log("Error while executing gm commands" + err);
return;
} else {
console.log("Done See your image");
}
})
};
gmcreateImage();

As noted in another response, graphicsmagick doesn't allow a buffer as an input. However, interestingly, it does allow an HTTP URL as an input. If you are determined to get this to work without modifying graphicsmagick's source code, try something like this:
setup
Add the uuid module:
npm install uuid
Create a global object to hold the buffers:
const magickbuffers = {}
Create an http server in your script:
const uuid = require('uuid')
const http = require('http')
const magickserver = new http.Server()
const magickport = 9555
magickserver.on('request', (req, res) => {
res.writeHead(200, { 'Content-Type': 'image/png' })
if (magickbuffers[req.url]) {
res.end(magickbuffers[req.url])
}
else {
res.end("\n")
}
})
magickserver.listen(magickport, () => {})
Create a prototype to supply a usable local url so graphicsmagick can find the buffer on the local server and hand it off as an image, and also to handle the storage of the buffer:
Object.prototype.openBuffer = function() {
this.id = uuid()
magickbuffers[`/${this.id}`] = this
return `http://localhost:${magickport}/${this.id}`
}
Create another prototype to handle the cleanup of the buffer once you're completely done with it:
Object.prototype.closeBuffer = function() {
delete magickbuffers[`/${this.id}`]
return true
}
usage
Old example that is desired but doesn't work:
gm()
.in('-page', '+0+0')
.in(bufferone) // gm cant use a buffer here, this won't work
.in('-page', '+50+20')
.in(buffertwo) // or here
.mosaic()
.stream('png', function (err, stdout, stderr) {
stdout.pipe(writestream)
})
New method that works:
let one = bufferone.openBuffer()
let two = buffertwo.openBuffer()
gm()
.in('-page', '+0+0')
.in(one) // gm recognizes this because 'one' is a url pointing to the buffer
.in('-page', '+50+20')
.in(two)
.mosaic()
.stream('png', function (err, stdout, stderr) {
stdout.pipe(writestream)
bufferone.closeBuffer() // do this once gm has completely finished using these buffers
buffertwo.closeBuffer() // don't forget to do this for each buffer manipulated with .openBuffer()
})

I haven't figured out how to do it with both an image and the watermark as buffers, but I have figured out how to keep the image as a buffer:
gm(imageBuffer)
.composite('./logo_path.png')
.geometry(geometry)
.gravity('SouthEast')
.dissolve(this.options.opacity)
.toBuffer(function (err, buffer) {
next(err, buffer, 'image/jpeg');
});
};
Check out the code in this great library for more info.

Related

Put images on video with ffmpeg in Node.js

I am creating a server-side video renderer in node.js. I need to add images on an existing video file every frame. I need to set the specific position of each frame in the rendered video. I am holding all the frames of the images in Readable. This is my code that works, but does not take into account the position of the images. How can I modify it? Of course I have a list with images coordinates - but I don't know how to make a filter out of this.
this.stream is a list of images.
const filter = ["[1:v]format=argb,setpts=PTS+" + 0 + "/TB[out]",
{
filter: "overlay",
options: {
enable: "between(t," + 0 + "," + 9 + ")",
x: "0",
y: "0",
},
inputs: "[0:v][out]",
outputs: "tmp",
}
]
const Options = [
"-crf 16",
"-f mp4",
"-vcodec libx264",
"-movflags frag_keyframe+empty_moov",
"-pix_fmt yuv420p",
];
var ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffm.path);
var command = ffmpeg();
const outputStream = fs.createWriteStream(this.Path + "test.mp4");
command.input(this.Path + "input.mp4");
command.input(this.stream).inputFPS(23);
command.outputOptions(Options);
command.fps(23);
command.complexFilter(filter, "tmp");
command.output(outputStream);
Instead of using const filter I suggest using the build-in fluent-ffmpeg functions. This code will add an image positioned (50, 50):
video_path and image_path are file paths to video and image.
overlay=50:50 will place your image at position x=50, y=50 from top-left.
Then you save the file and handle errors
const command = ffmpeg(video_path)
.input(image_path)
.complexFilter([
`overlay=50:50`,
])
.saveToFile("./public/vid.mp4")
.on("error", (err) => {
console.log(err);
})
.on("end", () => {
console.log("File saved.");
});
For more info see github issue: https://github.com/fluent-ffmpeg/node-fluent-ffmpeg/issues/770

Can GraphicsMagick for Node.js process a non-fixed number of images to output a GIF sequence?

I'm using GraphicsMagick for Node.js to generate a GIF sequence from 16 jpg images.
This works very well.
However currently I hardcoded the number of images in the sequence, when I use the function gm().in
I can't find a way or another function to make this number dynamic.
Here is my code:
var tmp_filenames = [];
var nNbStates = 16;
for (var nStateNum=0; nStateNum < nNbStates; nStateNum++)
{
tmp_filenames.push('mypath\myinputfilename' + nStateNum + '.jpg');
}
var gm = require('gm');
gm()
.in(tmp_filenames[0])
.in(tmp_filenames[1])
.in(tmp_filenames[2])
.in(tmp_filenames[3])
.in(tmp_filenames[4])
.in(tmp_filenames[5])
.in(tmp_filenames[6])
.in(tmp_filenames[7])
.in(tmp_filenames[8])
.in(tmp_filenames[9])
.in(tmp_filenames[10])
.in(tmp_filenames[11])
.in(tmp_filenames[12])
.in(tmp_filenames[13])
.in(tmp_filenames[14])
.in(tmp_filenames[15])
.write(output_filename, function (err) {
if (!err) console.log('gif file created!');
});
Thanks for reading.
OK I found a solution.
I was not familiar with this way of using variables and functions
This should work:
var tmp_filenames = [];
var nNbStates = 16;
var bmp = gm();
for (var nStateNum=0; nStateNum < nNbStates; nStateNum++)
{
tmp_filenames.push('mypath\myinputfilename' + nStateNum + '.jpg');
bmp.in(tmp_filenames[nStateNum])
}
.write(output_filename, function (err) {
if (!err) console.log('gif file created!');
});

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

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.

Writing png after capturing from webcam in node-webkit not working

I am taking snapshot in html5 using the following code.
window.takeSnapshot = function(){
$("body").append("<canvas id='dummyCanvas' class='canvas' style='display: none'></canvas>");
var canvas = document.getElementById("dummyCanvas");
canvas.width = videoWidth;
canvas.height = videoHeight;
var context = canvas.getContext("2d");
var type = mediaType;
var tp = tupple;
context.drawImage(videoElement, 0, 0, videoWidth, videoHeight);
var contents = canvas.toDataURL('image/png');
var dt = new Date();
Message.showProgress();
var time = dt.getHours() + "_" + dt.getMinutes() + "_" + dt.getSeconds();
var file = {name: "Snapshot_" + time + ".png", contents: contents, recorded: true};
var id = "attachment_" + window.Guid();
var icon = (type==ContentTypes.Video)?("video.png"):((type==ContentTypes.Audio)?"audio.png":"image.png");
$("#attachments").append("<tr id='"+id+"'><td align='right'><img src='assets/images/progress.gif' style='width:16px'/></td><td><img src='assets/images/" + icon + "' style='width: 14px;' /></td><td style='font-size: 8pt; font-family: Arial; font-weight: bold;' style='text-decoration:none; color: #000000'>"+file.name+"</td></tr>");
Logger.log("Uploading " + file.name + " ...", LogTypes.INFO);
$("#mediaPanel").remove();
$("#attachmentPopup").show();
window.stream.stop();
var callback = function(){
Logger.log("Upload completed for " + file.name + " !", LogTypes.INFO);
CWDocumentWriter.addAttachment(tp, file.name, type);
$("#"+id).find('td').eq(0).html("<a href='javascript:void(0)' title='Delete attachment' onclick='window.deleteAttachment(["+tp[0]+","+tp[1]+","+tp[2]+","+tp[3]+"],\""+file.name+"\", event)'><img src='assets/images/delete.png' style='width:16px'/></a>");
$("#"+id).find('td').eq(2).html("<a href='javascript:void(0)' title='"+file.name+"' onclick='window.showContent(\"" + file.name + "\", " + type + ")'>"+file.name+"</a>");
Message.hideProgress();
};
if(Cloud.isLive())
Cloud.writeFile(file, contents, callback);
else{
var canvasImage = canvas.toDataURL("image/png").split(',')[1];
var decodedImg = window.atob(canvasImage);
var img = new Buffer(decodedImg, encoding='base64');
file.contents = img;
StorageManager.writeImageFile(file, contents, callback);
}
};
The image is uploaded to the cloud or saved on the local storage (using nodejs functions) depending upon the live or dead internet. It works fine in uploading the image data to the cloud and I can save and see the image back. But in the case of local hard drive it is not working. The image seems to be corrupted. Here is how I am saving it.
StorageManager.writeImageFile = function(file, data, callback){
if(!UserManager.isLoggedIn()){
UserManager.login();
return;
}
var key = ProjectManager.projectName;
var dir = Settings.USER_FOLDER + "/" + key + "/" + "media";
data = window.encode64(file.contents);
fs.writeFile(dir + "/" + file.name, data, "base64", function(err){});
callback();
};
I have tried several approaches but it seems to be not working correctly. The image file is written in the hard drive but it is corrupted. Please help me to resolve this problem.
I believe this will solve your problem:
var imgData = canvas.toDataURL('image/png');
var data = imgData.replace(/^data:image\/\w+;base64,/, "");
var buf = new Buffer(data, 'base64');
var whereToSave = "C:\pathToSave"; // edit this
fs.writeFile(whereToSave, buf);

Resources