Watermarking an image on AWS Lambda with node.js and gm - node.js

I'm trying to resize and watermark an image (downloaded from S3) in an AWS Lambda function.
The resizing part is working well, based on the sample code from the "getting started" project of AWS Lambda.
Now, I have a problem adding a watermark to my files.
On my local system, I can do this:
gm('martinrose.jpg')
.draw(['image Over 0,0 0,0 wm-bas.png'])
.write('brol.jpg', function(e){
console.log(e||'done');
});
And it works without problem.
In the Lambda environment, I added the wm-bas.png file to the zip file uploaded to Amazon, and it seems to be found by my js code (I tested using lstatSync), but the real watermarking does not work.
Here is the relevant part of what I do:
gm(response.Body).size(function(err, size) {
var scalingFactor = Math.min(
newSize / size.width,
newSize / size.height
);
var width = scalingFactor * size.width;
var height = scalingFactor * size.height;
var fs = require('fs');
var stats = fs.lstatSync('wm-bas.png');
console.log(stats); // this outputs meaningful info, so, the file exists
var ctx = this.resize(width, height);//this works
if (shouldWatermark)
{
console.log("trying to watermark");
ctx = ctx.draw(['image Over 0,0 0,0 wm-bas.png']) //this doesn't work, although the previous log is written
}
ctx.toBuffer(imageType, function(err, buffer)
{
if (err) {
next(err);
} else {
next(null, response.ContentType, buffer);
}
}
);
});
What am I missing? Why doesn't this work? Is it related to the fact that I save in a buffer and not in a file?
I import gm with this code, BTW:
var gm = require('gm')
.subClass({ imageMagick: true });

You need to package all of your node_modules with your Lambda deployment. Install your modules locally in your project and package them with your Lambda code. Another very important fact is Amazon Lambda still relies on installed system libraries. Your Node.js module may be using a library that may not be installed on the system where Lambda is executed, you need to package everything with your Lambda deployment.
See this official post about modules and
Node.js packages in Lambda

I have used "sharp" library before to add a text watermark with a custom font using a Nodejs lambda function. I wrote a story on Medium you can read Watermark with an AWS lambda
//...
const textedSVG = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg"
xml:lang="en"
height="40"
width="200">
<text
font-family="MyFont"
font-style="italic"
x="0" y="20" font-size="16" fill="#fff">
${process.env.WATERMARK_TEXT}
</text></svg>`);
let imgDst = sharp(origimage.Body);
var buffer = await imgDst
.composite([
{
input: textedSVG,
gravity: "southeast",
},
])
// Use the Sharp module to resize the image and save in a buffer.
.resize(width)
.jpeg({ quality: 70 }) //decrease the image quality
.toBuffer();
//...

Related

Download image and resize in nodejs

What I am trying to do is download an image from google into my system repository in the project folder /download. Next, I am trying to get the image from the download repository and resize and again save the resized image in /thumbnail repository. Below is the code which I have written
//Google URL
var mainuri = 'http://images.sadhguru.org/sites/default/files/media_files/iso/en/64083-natures-temples.jpg';
var dir = './download';
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
// CODE TO DOWNLOAD IMAGE FROM GOOGLE
var filename = dir+'/file.jpg';
request.head(mainuri, function(err, res, body){
console.log('content-type:', res.headers['content-type']);
console.log('content-length:', res.headers['content-length']);
request(mainuri).pipe(fs.createWriteStream(filename));
});
var resizedir = './thumbnail';
if (!fs.existsSync(resizedir)){
fs.mkdirSync(resizedir);
}
//CODE TO GET IMAGE FROM '/download' REPOSITORY AND RESIZE
var inputFile=require('path').join(__dirname,'../download/file.jpg'); //GET IMAGE FROM LOCAL
var outFile = 'thumbnail/newfile.jpg'; // SAVE RESIZED IMAGE PATH
// ------- CODE REMOVED - ERROR IS COMING HERE -----------
imageresize(inputFile, outFile); // FUNCTION THAT CONTAINS IMAGE RESIZE CODE
//imageresize FUNCTION
var imageresize = function(inputFile, outFile){
console.log(inputFile)
// input stream
let inStream = fs.createReadStream('C:/Users/rganji/caw/server/download/file.jpg');
// output stream
let outStream = fs.createWriteStream(outFile, {flags: "w"});
// on error of output file being saved
outStream.on('error', function() {
console.log("Error");
});
// on success of output file being saved
outStream.on('close', function() {
console.log("Successfully saved file");
});
// input stream transformer
// "info" event will be emitted on resize
let transform = sharp()
.resize({ width: 50, height: 50 })
.on('info', function(fileInfo) {
console.log("Resizing done, file not saved");
});
inStream.pipe(transform).pipe(outStream);
}
If I remove the code which I commented as CODE REMOVED - ERROR IS COMING HERE, then image is getting downloaded from google. If I call imageresize function then the /download directory is getting created but I couldnt find any image in the directory.
i.e, If I call the download from google and resizeimage functions separately, i.e calling image download from google first and imageresize next then they are working fine that is I can find images in both /download and /thumbnail directories. But If I call resizeimage function after google download then I couldnt find any image in both the repositories.
You need to wait for the image download to get finished before calling the imageresize function. Try this
request(mainuri).on('end', function() {
var inputFile=require('path').join(__dirname,'../download/file.jpg'); //GET IMAGE FROM LOCAL
var outFile = 'thumbnail/newfile.jpg'; // SAVE RESIZED IMAGE PATH
imageresize(inputFile, outFile);
});

Canvas Image manipulation in Firebase Functions

I am trying to replicate some client-side code, using firebase functions. Basically, I am just trying to take a user image, create a circular version of it with a color around it.
Here is the code that I am currently using, which works on the client side of things, but not in Firebase Functions.
export async function createCanvasImage(userImage) {
console.log('createCanvasImage started...');
const canvas = new Canvas(60, 60);
const context = canvas.getContext('2d');
// Creates the blue user image.
async function blueCanvas() {
return new Promise((resolve, reject) => {
context.strokeStyle = '#488aff';
context.beginPath();
context.arc(30, 30, 28, 0, 2 * Math.PI);
context.imageSmoothingEnabled = false;
context.lineWidth = 3;
context.stroke();
context.imageSmoothingEnabled = false;
console.log('Canvas Image loaded!');
context.save();
context.beginPath();
context.arc(30, 30, 28, 0, Math.PI * 2, true);
context.closePath();
context.clip();
context.drawImage(userImage, 0, 0, 60, 60);
context.beginPath();
context.arc(0, 0, 28, 0, Math.PI * 2, true);
context.clip();
context.closePath();
context.restore();
const dataURL = canvas.toDataURL();
console.log(dataURL);
resolve(dataURL);
});
}
But it is, unfortunately, returning the following error:
Error creating one of the canvas images... TypeError: Image or Canvas expected
I suspect it's because the image isn't loading properly, but I'm not exactly sure how to get it to load on the server properly.
I was trying to use some code that is provided in the firebase function samples:
return mkdirp(tempLocalDir)
.then(() => {
// Download file from bucket.
return bucket.file(filePath)
.download({ destination: tempLocalFile });
})
.then(() => {
console.log('The file has been downloaded to', tempLocalFile);
createCanvasImage(tempLocalFile)
.catch(err => {console.log(err); });
})
.catch(err => { console.log(err); });
I think you'll be missing the DOM, unless you're importing libraries that aren't shown here. This is the same reason Nodejs doesn't have a window or document object. There is a good Q&A here Why doesn't node.js have a native DOM?
There is a node canvas implementation node-canvas which can include in your package.
If you weren't missing the DOM as Lex suggested, I found this and this post where the issue was the node-canvas version.
I only see Canvas in one module but if you were using more, read the explanation below I found here:
I can't find the other issue that discusses this more, but there is no
way to fix this in node-canvas itself because it's a limitation of
native addons (two different builds of node-canvas don't produce
compatible objects). At some level, your JS code needs to make sure
that it's using the same node-canvas everywhere. Without knowing
anything about your application, this may mean using npm v3 or later,
which has a flat(ter) node_modules directory; ensuring that everything
that depends on node-canvas is allowed to use the same version; and/or
providing a central wrapper for node-canvas that you require instead
of requiring node-canvas directly. The first two should hopefully Just
Work and not require code changes, but may be brittle.

Upload Full Imagemagick to AWS Lambda and use with graphics magic module

Here is a prior post that discusses how the pre-loaded Imagemagick is limited for security reasons on AWS Lambda.
"Note: This update contains an updated /etc/ImageMagick/policy.xml
file that disables the EPHEMERAL, HTTPS, HTTP, URL, FTP, MVG, MSL,
TEXT, and LABEL coders"
I need to use the 'label' function (which works successfully on my development machine - example pic further below))
Within the discussion in the linked post, frenchie4111 generously offers use of a node module he created that uploads imagemagick to a lambda app: github link https://github.com/DoubleDor/imagemagick-prebuilt
I would like to understand how uploading a fresh version of Imagemagick works, and how I will then use that version with the GM module that incorporates IM and nodejs together.
If I read correctly the full version of imagemagick will be reloaded to the address below each time my lambda app boots up ?
/tmp/imagemagick
DoubleDor's readme directions provides the option below:
var imagemagick_prebuilt = require( 'imagemagick-prebuilt' );
var child_process = require( 'child_process' );
exports.handler = function( event, context ) {
return q
.async( function *() {
imagemagick_bin_location = yield imagemagick_prebuilt();
console.log( `ImageMagick installed: ${imagemagick_bin_location}` );
// ImageMagick logo creation test:
// convert logo: logo.gif
var convert_process = child_process
.spawn( imagemagick_bin_location, [ 'logo:', 'logo.gif' ] )
convert_process
.on( 'close', function() {
context.success();
} );
} )();
};
What would I include/require to define 'gm' to work within my partial file below (in my nodejs lambda app)?
Will I need to edit the GM module too?
//imagemaker.js > gets included and called from another file that uploads picture to s3, and/or tweets it after picture is created in /tmp/filename.jpg This works presently.. I can make and upload imagemagick text generated images but I just can't use the 'label' tool which scales text within appended gm images
'use strict';
var Promise = require('bluebird');
var exec = require('child_process').exec;
var async = require('async');
var request = require('request');
var fs = require('fs');
var dateFormat = require('dateformat');
var gm = require('gm').subClass({imageMagick: true});
var aws = require('aws-sdk');
performers = [ { name: 'Matt Daemon', score: 99}, { name: “Jenifer Lawrence”, score: 101}
//created in a makeTitle function I omit for brevity sake.
url = “/temp/pathtotitlepicture.jpg”
// function below successfully appends a gm title image created with other functions that I haven't included
function makeBody(url){
var img = gm(400,40)
.append(url)
.gravity('West')
.fill('black')
.quality('100')
.font('bigcaslon.ttf')
.background('#f0f8ff')
for (var i = 0; i < performers.length; i++) {
var pname = " " + (i+1) + ") " +performers[i].name;
img.out('label:'+pname);
};
img.borderColor('#c5e4ff')
.border('5', '5')
.write(url, function (err) {
if (err) throw err;
var stream = fs.createReadStream("/tmp/fooberry.jpg")
return resolve(stream)
});
}
Just for fun, the image below shows what I've been able to do with gm(graphics magic) and imagemagick on my development machine that I'd now like to get working on AWS Lambda >> I really need that 'label' function and I guess that means learning how to get that whole library uploaded to AWS Lambda each time it boots!(?)

Write text on existing PNG with Node.js

I'm trying to create a simple dynamic-badge (png) to embed in static pages to let know the status of my application.
I'd like so to use an existing PNG image and write on it some text with Node.js.
I've found lot of libraries but all of them use Imagemagick or Cairo as native dependencies, I'd like to avoid to install anything else on the server.
I've then found lwip, but I can't really understand how to write text on an image with it. How can I do?
You could use Jimp. It has a print method:
var Jimp = require("jimp");
var fileName = 'test.png';
var imageCaption = 'Image caption';
var loadedImage;
Jimp.read(fileName)
.then(function (image) {
loadedImage = image;
return Jimp.loadFont(Jimp.FONT_SANS_16_BLACK);
})
.then(function (font) {
loadedImage.print(font, 10, 10, imageCaption)
.write(fileName);
})
.catch(function (err) {
console.error(err);
});
If you want to use a ttf file you can use gm
This will also align the text automatically so you don't have to keep track of your letter sizing / location.
const gm = require('gm').subClass({imageMagick: true});
gm('path/to/image.png')
.fill("#FFFFFF")
.font("./font.ttf", 20)
.drawText(15, 10, "your text", 'southeast') //can be any location
.write("./output.png", function (err) {
if (!err) console.log('done');
});

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