Upload Full Imagemagick to AWS Lambda and use with graphics magic module - node.js

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!(?)

Related

Anystock not working with Anychart-NodeJS

I'm setting up a node.js server that renders static jpg/png images using Anychart.
It is possible for me to return the simple example pie charts in the examples but when I try to return the examples for AnyStock, I get some weird results.
The code should create and return a stock chart on the url: xx.xxx.xxx.xx:3000/insert.
Instead the code returns this chart without any graphs or candlesticks:
When I set the same graph up on a plain html site, I get following result:
The node.js code:
var fs = require('fs');
var express = require('express');
var app = express();
var path = require('path');
var router = express.Router();
app.get('/', function(req, res) {
var query = require('url').parse(req.url, true).query;
var stock_id = query.stock_id;
var type = query.type;
if (type == "insert") {
var JSDOM = require('jsdom').JSDOM;
var jsdom = new JSDOM('<head><script src="https://cdn.anychart.com/releases/8.9.0/js/anychart-core.min.js" type="text/javascript"></script><script src="https://cdn.anychart.com/releases/8.9.0/js/anychart-stock.min.js" type="text/javascript"></script></head><body><div id="container" style="width: 500px; height: 400px;"></div></body>', {
runScripts: 'dangerously'
});
var window = jsdom.window;
var anychart = require('anychart')(window);
var anychartExport = require('anychart-nodejs')(anychart);
var table, mapping, chart;
table = anychart.data.table();
table.addData([
['2015-12-24', 511.53, 514.98, 505.79, 506.40],
['2015-12-25', 512.53, 514.88, 505.69, 507.34],
['2015-12-26', 511.83, 514.98, 505.59, 506.23],
['2015-12-27', 511.22, 515.30, 505.49, 506.47],
['2015-12-28', 510.35, 515.72, 505.23, 505.80],
['2015-12-29', 510.53, 515.86, 505.38, 508.25],
['2015-12-30', 511.43, 515.98, 505.66, 507.45],
['2015-12-31', 511.50, 515.33, 505.99, 507.98],
['2016-01-01', 511.32, 514.29, 505.99, 506.37],
['2016-01-02', 511.70, 514.87, 506.18, 506.75],
['2016-01-03', 512.30, 514.78, 505.87, 508.67],
['2016-01-04', 512.50, 514.77, 505.83, 508.35],
['2016-01-05', 511.53, 516.18, 505.91, 509.42],
['2016-01-06', 511.13, 516.01, 506.00, 509.26],
['2016-01-07', 510.93, 516.07, 506.00, 510.99],
['2016-01-08', 510.88, 515.93, 505.22, 509.95],
['2016-01-09', 509.12, 515.97, 505.15, 510.12],
['2016-01-10', 508.53, 516.13, 505.66, 510.42],
['2016-01-11', 508.90, 516.24, 505.73, 510.40]
]);
mapping = table.mapAs();
mapping.addField('open', 1, 'first');
mapping.addField('high', 2, 'max');
mapping.addField('low', 3, 'min');
mapping.addField('close', 4, 'last');
mapping.addField('value', 4, 'last');
chart = anychart.stock();
chart.plot(0).ohlc(mapping).name('ACME Corp.');
chart.title('AnyStock Basic Sample');
chart.container('container');
chart.draw();
anychartExport.exportTo(chart, 'jpg').then(function(image) {
fs.writeFile('anychart.jpg', image, function(fsWriteError) {
if (fsWriteError) {
console.log(fsWriteError);
} else {
res.sendFile(path.join(__dirname + '/anychart.jpg'));
}
});
}, function(generationError) {
console.log(generationError);
});
} else if (type == "image") {
res.sendFile(path.join(__dirname + '/anychart.jpg'));
}
});
app.listen(3000);
I suspect there's something wrong with the way I includes the JS-files in the jsdom. If I exclude the two files in the jsdom, I get the same result..
Please let me know if you have any suggestions.
Can you please check and share the browser console messages? That tends to be the first troubleshooting step :)
So after a few days of waiting time, the AnyChart Support returned to me with the following answer for my question above:
we can't guarantee that this module will work as expected. It depends
on many other libraries that can't provide stable versions in
different OS.
Instead they recommend to use their Export Server solution which is different from what I was looking for.
Our setup is built on a LAMP-server, so we don't want to run another server just for a few images a day.
If any of you have a suggestion for a solution where I can export my AnyStock charts to JPG, PNG or GIFs please let me know.
Thanks :-)

Generating a json for a icon cheatsheet

I'm trying to generate a json file containing the filenames of all the files in a certain directory. I need this to create a cheatsheet for icons.
Currently I'm trying to run a script locally via terminal, to generate the json. That json will be the input for a react component that will display icons. That component works, the create json script doesn't.
Code for generating the json
const fs = require('fs');
const path = require('path');
/**
* Create JSON file
*/
const CreateJson = () => {
const files = [];
const dir = '../icons';
fs.readdirSync(dir).forEach(filename => {
const name = path.parse(filename);
const filepath = path.resolve(dir, filename);
const stat = fs.statSync(filepath);
const isFile = stat.isFile();
if (isFile) files.push({ name });
});
const data = JSON.stringify(files, null, 2);
fs.writeFileSync('../Icons.json', data);
};
module.exports = CreateJson;
I run it in terminal using
"create:json": "NODE_ENV=build node ./scripts/CreateJson.js"
I expect a json file to be created/overridden. But terminal returns:
$ NODE_ENV=build node ./scripts/CreateJson.js
✨ Done in 0.16s.
Any pointers?
You are creating a function CreateJson and exporting it, but you are actually never calling it.
You can get rid of the module.exports and replace it with CreateJson().
When you'll execute the file with node, it will see the function declaration, and a call to it, whereas with your current code there is no call.

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

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();
//...

Synchronously load a dependency in node.js

I'm loading a node library in script and immediately after loading some customization that depends on that library:
var somelib = require('somelib');
// some customizations made to
// somelib's methods
require('./somelib.custom');
somelib.SomeObject = ...
^
ReferenceError: somelib is not defined
I keep getting an exception since the loading is done asynchronously and the second require happens before the first is namespaced correctly. What's a good way to resolve this? thanks.
EDIT: My original code
I'm trying to create a PNG image from json data using fabric.js node package (building on the article in package site). This is done by loading the server-side fabric canvas with JSON data that was originally generated on the client, then writing to a file stream:
var path = require('path');
var fs = require('fs');
var fabric = require('fabric').fabric;
require('./fabric.custom');
var canvas = fabric.createCanvasForNode(400, 400);
var outfile = fs.createWriteStream("image.png");
var filepath = "/path/to/file.json";
fs.readFile(filepath, 'utf8', function(err, json) {
canvas.loadFromJSON(json, function() {
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
outfile.write(chunk);
});
});
});
The "fabric.custom" file holds several custom fabric canvas objects that override some fabric prototype defaults. They work well on the client, and are needed to properly render the canvas. It looks something like this:
fabric.TextBox = fabric.util.createClass(fabric.Text, {
type: 'text-box',
// more object specific stuff ...
});
Thanks.
Rather than relying on side effects in a require to mutate your fabric object, how about having the fabric.custom.js file export the modified fabric, like so?
fabric.custom.js:
var fabric = require('fabric').fabric;
fabric.myCustomMethod = function(){ ... }
...
module.exports = fabric; // the modified fabric
And in your main file:
var path = require('path');
var fs = require('fs');
// var fabric = require('fabric').fabric; No need for this line anymore
var modifiedFabric = require('./fabric.custom');
...
modifiedFabric.myCustomMethod( ... ); // should now be available

Why append rather than write when using knox / node.js to grab file from Amazon s3

I'm experimenting with the knox module for node.js as a way of managing some small files in an Amazon S3 bucket. Everything works fine stand-alone: I can upload a file, download a file, etc. However, I want to be able to download a file on recurring schedule. When I modify the code to run on an interval, I'm getting the downloaded file appending to the previous instance instead of overwriting.
I'm not sure if I've made a mistake in the file write code or in the knox handling code. I've tried several different write approaches (writeFile, writeStream, etc.) and I've looked at the knox source code. Nothing obvious to me stands out as a problem. Here's the code I'm using:
knox = require('knox');
fs = require('fs');
var downFile = DOWNFILE;
var downTxt = '';
var timer = INTERVAL;
var path = S3PATH + downFile;
setInterval(function()
{
var s3client = knox.createClient(
{
key: '********************',
secret: '**********************************',
bucket: '********'
});
s3client.get(path).on('response', function(response)
{
response.setEncoding('ascii');
response.on('data', function(chunk)
{
downTxt += chunk;
});
response.on('end', function()
{
fs.writeFileSync(downFile, downTxt, 'ascii');
});
}).end();
},
timer);
The problem is with your placement of var downTxt = '';. That is the only place you set downTxt to blank, so every time you retrieve more data, you add it to the data that you got in the previous request because you never clear the data from the previous request. The simplest fix is to move that line to just before the setEncoding line.
However, the way you are processing the data is unnecessarily complicated. Try something like this instead. You don't need to recreate the client every time, and setting the encoding will just break things if you are downloading non-text files, and it won't make a difference with text files. Next, you shouldn't manually collect the data, you can immediately start writing it to the file as you receive it. Lastly, since request is a standard stream, you don't need to monitor the 'data' event because you can just use pipe.
var knox = require('knox'),
fs = require('fs'),
downFile = DOWNFILE,
timer = INTERVAL,
path = S3PATH + downFile,
s3client = knox.createClient({
key: '********************',
secret: '**********************************',
bucket: '********'
});
(function downloadFile() {
var str = fs.createWriteStream(downFile);
s3client.get(path).pipe(str);
str.on('close', function() {
setTimeout(downloadFile, timer);
});
})();

Resources