Skip to content

Commit 1d4192a

Browse files
committed
Updated MemeNG.
Added meme generating functionality. Replaced old memegen.link meme generation functionality with new custom meme generator.
1 parent eda1ef6 commit 1d4192a

File tree

9 files changed

+347
-60
lines changed

9 files changed

+347
-60
lines changed

app/js/memeng.js

+232-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ let https = require('https');
99
let fs = require('fs');
1010
let crypto = require('crypto');
1111
let sh = require('shorthash');
12+
// http://stackoverflow.com/a/17133012
13+
// Must set encoding to null for the response body type to be buffer
14+
let request = require('request').defaults({encoding: null});
15+
16+
let Canvas = require('canvas');
17+
let Image = Canvas.Image;
18+
1219

1320
let firebase = require('./firebase');
1421
let GCloud = require('gcloud');
@@ -66,13 +73,34 @@ var MemeNG = function (config) {
6673
// console.log('Storage bucket: ');
6774
// console.log(this.storage);
6875

76+
// meme image canvas attributes
77+
this.canvasWidth = 700;
78+
this.canvasHeight = 700;
79+
80+
this.memeWidth = this.canvasWidth;
81+
this.memeHeight = this.canvasHeight;
82+
83+
this.canvas = new Canvas(this.canvasWidth, this.canvasHeight);
84+
this.ctx = this.canvas.getContext('2d');
85+
86+
this.canvasImg = new Image();
87+
6988
return this;
7089
};
7190

91+
/**
92+
* Get data for all the available memes
93+
* @returns {!firebase.Promise.<*>|firebase.Promise<any>}
94+
*/
7295
MemeNG.prototype.getMemes = function () {
7396
return this.database.ref('/memes').once('value');
7497
};
7598

99+
/**
100+
* Get the details of the meme with the specified id
101+
* @param id
102+
* @returns {!firebase.Promise.<*>|firebase.Thenable<any>|firebase.Promise<any>}
103+
*/
76104
MemeNG.prototype.getMemeDetails = function (id) {
77105
return this.database.ref('/memes/' + id).once('value').then(function (snapshot) {
78106
return snapshot.val();
@@ -81,18 +109,46 @@ MemeNG.prototype.getMemeDetails = function (id) {
81109

82110
/**
83111
* returns the URL of the generated meme
112+
* @version 1.0
84113
* @param opts.id
85114
* @param opts.top
86115
* @param opts.bottom
87116
*
88117
*/
89-
MemeNG.prototype.createMeme = function (opts) {
118+
MemeNG.prototype.createMemeLink = function (opts) {
90119
var id = opts.id;
91120
var topText = opts.top;
92121
var bottomText = opts.bottom;
93122

94123
var that = this;
95124

125+
return new Promise(function(resolve, reject){
126+
that.getMemeTemplateURL(id).then(function(imageURL){
127+
128+
var url = 'https://memegen.link/custom/<top>/<bottom>.jpg?alt=' + encodeURIComponent(imageURL);
129+
130+
// var url = 'https://memegen.link/api/templates/' + id + '/' + MemeNG.encodeMemeText(topText) + '/' + MemeNG.encodeMemeText(bottomText) + '';
131+
url = url
132+
.replace('<top>', MemeNG.encodeMemeText(topText) || '')
133+
.replace('<bottom>', MemeNG.encodeMemeText(bottomText) || '');
134+
135+
var result = {
136+
url: url,
137+
opts: opts
138+
};
139+
resolve(result);
140+
}).catch(reject);
141+
});
142+
143+
};
144+
145+
/**
146+
* Generate a valid URL for the meme template
147+
* @param id
148+
* @returns {Promise}
149+
*/
150+
MemeNG.prototype.getMemeTemplateURL = function(id){
151+
var that = this;
96152
return new Promise(function(resolve, reject){
97153
// If the meme doesn't exist, don't continue
98154
// if(!this.memeExists(id))return false;
@@ -132,23 +188,11 @@ MemeNG.prototype.createMeme = function (opts) {
132188
return false;
133189
}
134190

135-
var url = 'https://memegen.link/custom/<top>/<bottom>.jpg?alt=' + encodeURIComponent(imageURL);
136-
137-
// var url = 'https://memegen.link/api/templates/' + id + '/' + MemeNG.encodeMemeText(topText) + '/' + MemeNG.encodeMemeText(bottomText) + '';
138-
url = url
139-
.replace('<top>', MemeNG.encodeMemeText(topText) || 'top')
140-
.replace('<bottom>', MemeNG.encodeMemeText(bottomText) || 'bottom');
141-
142-
var result = {
143-
url: url,
144-
opts: opts
145-
};
146-
resolve(result);
191+
resolve(imageURL);
147192
});
148193
});
149194
});
150195
});
151-
152196
};
153197

154198
/**
@@ -169,6 +213,52 @@ MemeNG.prototype.authenticate = function (email, password) {
169213
});
170214
};
171215

216+
MemeNG.prototype.generateMemeCanvas = function(config){
217+
var defaults = {
218+
top: '',
219+
bottom: '',
220+
imageURL: ''
221+
};
222+
var opts = Object.assign({}, defaults, config);
223+
224+
var that = this;
225+
226+
console.log(opts);
227+
228+
return new Promise(function(resolve, reject){
229+
request.get(opts.imageURL, function(err, response, body){
230+
231+
if(!err && response.statusCode == 200){
232+
that.canvasImg.src = new Buffer(body);
233+
console.log('image loaded.', new Buffer(body));
234+
that.calculateCanvasSize();
235+
that.drawMeme(opts.top, opts.bottom);
236+
resolve(that.canvas.toBuffer());
237+
}
238+
else{
239+
reject(new Error('The image could not be loaded.'));
240+
}
241+
});
242+
});
243+
};
244+
245+
MemeNG.prototype.createMeme = function(opts){
246+
var id = opts.id;
247+
var topText = opts.top;
248+
var bottomText = opts.bottom;
249+
250+
var that = this;
251+
252+
return new Promise(function(resolve, reject){
253+
that.getMemeTemplateURL(id).then(function(imageURL){
254+
opts.imageURL = imageURL;
255+
that.generateMemeCanvas(opts).then((memeURL) => {
256+
resolve(memeURL);
257+
});
258+
}).catch((err) => {reject(new Error('Issue encountered generating template URL.', err.message))});
259+
});
260+
};
261+
172262
/**
173263
* encodes the text using the prescribed format from memegen.link
174264
* @param text
@@ -188,6 +278,11 @@ MemeNG.encodeMemeText = function (text) {
188278
.replace(/"/g, "''");
189279
};
190280

281+
/**
282+
* Downloads the file in the specified URL and returns the local URL
283+
* @param url
284+
* @returns {Promise}
285+
*/
191286
MemeNG.downloadFile = function (url) {
192287

193288
// let memeHash = crypto.createHash('md5').update(url).digest("hex");
@@ -207,7 +302,130 @@ MemeNG.downloadFile = function (url) {
207302

208303
};
209304

305+
MemeNG.prototype.calculateCanvasSize = function () {
306+
console.log(this.canvasImg.width, this.canvasImg.height);
307+
if(this.canvasImg.width > this.canvasImg.height){
308+
this.canvas.height = this.canvasImg.height / this.canvasImg.width * this.canvas.width;
309+
this.memeWidth = this.canvas.width;
310+
this.memeHeight = this.canvas.height;
311+
console.log(this.memeWidth, this.memeHeight);
312+
}
313+
return {width: this.memeWidth, height: this.memeHeight};
314+
};
315+
316+
MemeNG.prototype.drawMeme = function (topText, bottomText) {
317+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
318+
319+
this.ctx.drawImage(this.canvasImg, 0, 0, this.memeWidth, this.memeHeight);
320+
321+
this.ctx.lineWidth = 8;
322+
this.ctx.font = 'bold 50pt Impact';
323+
this.ctx.strokeStyle = 'black';
324+
this.ctx.mutterLine = 2;
325+
this.ctx.lineJoin = "miter"; //Experiment with "bevel" & "round" for the effect you want!
326+
this.ctx.miterLimit = 3;
327+
this.ctx.fillStyle = 'white';
328+
this.ctx.textAlign = 'center';
329+
this.ctx.textBaseline = 'top';
330+
331+
topText = topText.toUpperCase();
332+
var x = this.memeWidth / 2;
333+
var y = 0;
334+
335+
this.writeTextOnCanvas({
336+
text: topText,
337+
x: x,
338+
y: y,
339+
maxWidth: this.memeWidth,
340+
lineHeightRatio: 1.6,
341+
fromBottom: false,
342+
fontSize: 50
343+
});
210344

345+
this.ctx.textBaseline = 'bottom';
346+
bottomText = bottomText.toUpperCase();
347+
y = this.memeHeight;
348+
349+
this.writeTextOnCanvas({
350+
text: bottomText,
351+
x: x,
352+
y: y,
353+
maxWidth: this.memeWidth,
354+
lineHeightRatio: 1.6,
355+
fromBottom: true,
356+
fontSize: 50
357+
});
358+
};
359+
360+
MemeNG.prototype.writeTextOnCanvas = function (config) {
361+
var defaults = {
362+
text: 'MemeNG',
363+
x: 0,
364+
y: 0,
365+
maxWidth: 1000,
366+
lineHeightRatio: 2,
367+
fromBottom: false,
368+
fontSize: 50,
369+
maxLines: 2
370+
};
371+
var opts = Object.assign({}, defaults, config);
372+
373+
this.ctx.font = 'bold ' + opts.fontSize + 'pt Impact';
374+
375+
// If from the bottom, use unshift so the lines can be added to the top of the array.
376+
// Required since the lines at the bottom are laid out from bottom up.
377+
var pushMethod = (opts.fromBottom) ? 'unshift' : 'push';
378+
379+
var _lineHeightRatio = (opts.fromBottom) ? -opts.lineHeightRatio : opts.lineHeightRatio;
380+
var lineHeight = _lineHeightRatio * opts.fontSize;
381+
382+
console.log('lineH', lineHeight, opts.lineHeightRatio, opts.fontSize);
383+
384+
var lines = [];
385+
var line = '';
386+
var words = opts.text.split(' ');
387+
388+
for (var n = 0, len = words.length; n < len; n++) {
389+
// Create a line by adding the words one after the other
390+
var testLine = line + ' ' + words[n];
391+
392+
// Get the width of the line after adding the current word
393+
var metrics = this.ctx.measureText(testLine);
394+
var testWidth = metrics.width;
395+
396+
// Then checking the line width to see if it is more than the specified maxWidth
397+
if (testWidth > opts.maxWidth) {
398+
lines[pushMethod](line);
399+
line = words[n] + ' ';
400+
} else {
401+
line = testLine;
402+
}
403+
}
404+
// Don't forget to add the last line after the loop
405+
lines[pushMethod](line);
406+
407+
// Check to make sure the number of lines isn't more than the maximum number of lines
408+
// If it is, then reduce the font size and try again
409+
if(lines.length > opts.maxLines){
410+
console.log('Too big.', opts.fontSize);
411+
this.writeTextOnCanvas({
412+
text: opts.text,
413+
x: opts.x,
414+
y: opts.y,
415+
maxWidth: opts.maxWidth,
416+
lineHeightRatio: opts.lineHeightRatio,
417+
fromBottom: opts.fromBottom,
418+
fontSize: opts.fontSize - 10
419+
});
420+
}
421+
// If it isn't, then write the text
422+
else{
423+
for (var k in lines) {
424+
this.ctx.strokeText(lines[k], opts.x, opts.y + lineHeight * k);
425+
this.ctx.fillText(lines[k], opts.x, opts.y + lineHeight * k);
426+
}
427+
}
428+
};
211429
//---------
212430
/*
213431
Meme model

app/routes/api.js

+34-8
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,37 @@ router.get('/memes', (req, res) => {
1616
data = snapshot.val();
1717
res.send(data);
1818
// console.log(snapshot.val());
19+
}).catch(function(err){
20+
console.log(err.message);
1921
});
2022
});
2123

22-
router.get('/meme/:id/:top/:bottom', (req, res) => {
23-
memeng.createMeme({id: req.params.id, top: req.params.top, bottom: req.params.bottom}).then(function(parameters){
24-
var url = parameters.url;
24+
router.get('/meme/:id/:top/:bottom?', (req, res) => {
25+
26+
console.log(req.params.id, req.params.top, req.params.bottom);
27+
memeng.createMeme({id: req.params.id, top: req.params.top || '', bottom: req.params.bottom || ''}).then((meme) => {
2528

26-
MemeNG.downloadFile(url).then((result) => {
27-
res.sendFile(result.fileURL);
28-
});
29-
// res.send('<img src="' + url + '"/>');
29+
// res.send('<img src="' + memeURL + '" />');
30+
res.set('Content-Type', 'image/png');
31+
res.send(meme);
32+
// res.send(memeURL);
3033
}).catch(function(err){
3134
console.error(err.message);
3235
res.send('We couldnt get your meme. Sorry.');
3336
});
37+
38+
// Old implementation using memeLink from memegen.link
39+
// memeng.createMemeLink({id: req.params.id, top: req.params.top, bottom: req.params.bottom}).then(function(parameters){
40+
// var url = parameters.url;
41+
//
42+
// MemeNG.downloadFile(url).then((result) => {
43+
// res.sendFile(result.fileURL);
44+
// });
45+
// // res.send('<img src="' + url + '"/>');
46+
// }).catch(function(err){
47+
// console.error(err.message);
48+
// res.send('We couldnt get your meme. Sorry.');
49+
// });
3450
});
3551

3652
router.get('/authenticate/:email/:password', (req, res) => {
@@ -42,8 +58,18 @@ router.post('/template/save', (req, res) => {
4258

4359
});
4460

61+
router.get('/test', (req, res) => {
62+
memeng.createMeme({id: 'packing-chairs', top: 'Okay'}).then((meme) => {
63+
64+
// res.send('<img src="' + memeURL + '" />');
65+
res.set('Content-Type', 'image/png');
66+
res.send(meme);
67+
// res.send(memeURL);
68+
});
69+
});
70+
4571
// memeng.getMemeDetails('packing-chairs');
46-
// memeng.createMeme({id: 'jack-tunde', top: 'Over here', bottom: 'Understood'}).then(function(url){
72+
// memeng.createMemeLink({id: 'jack-tunde', top: 'Over here', bottom: 'Understood'}).then(function(url){
4773
// console.log('Final image URL: ', url);
4874
// }).catch(function(err){console.error(err.message)});
4975
module.exports = router;

0 commit comments

Comments
 (0)