diff --git a/app.js b/app.js index 3f02a61..b37c112 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,8 @@ const path = require('path'); const tmpDir = __dirname + '/tmp/'; const publicDir = __dirname + '/public/'; +const imagesDir = __dirname + '/images/'; +const defaultBgImage = 'bg-image.jpg'; // canvas generator const CountdownGenerator = require('./countdown-generator'); @@ -21,33 +23,39 @@ app.get('/', function (req, res) { // generate and download the gif app.get('/generate', function (req, res) { - let {time, width, height, color, bg, name, frames} = req.query; + let {time, width, height, color, bg, name, withImageBg, frames} = req.query; if(!time){ throw Error('Time parameter is required.'); } - CountdownGenerator.init(time, width, height, color, bg, name, frames, () => { + const imageBgSrc = `${imagesDir}${defaultBgImage}`; + + + CountdownGenerator.init(time, width, height, color, bg, name, (withImageBg ? imageBgSrc : null), frames, () => { let filePath = tmpDir + name + '.gif'; - res.download(filePath); + res.sendFile(filePath); }); }); // serve the gif to a browser app.get('/serve', function (req, res) { - let {time, width, height, color, bg, name, frames} = req.query; + let {time, width, height, color, bg, name, withImageBg, frames} = req.query; if(!time){ throw Error('Time parameter is required.'); } - CountdownGenerator.init(time, width, height, color, bg, name, frames, () => { + const imageBgSrc = `${imagesDir}${defaultBgImage}`; + + + CountdownGenerator.init(time, width, height, color, bg, name, (withImageBg ? imageBgSrc : null), frames, () => { let filePath = tmpDir + name + '.gif'; res.sendFile(filePath); }); }); -app.listen(process.env.PORT || 3000, function(){ +app.listen(process.env.PORT || 3500, function(){ console.log("Express server listening on port %d in %s mode", this.address().port, app.settings.env); }); diff --git a/countdown-generator/index.js b/countdown-generator/index.js index 1e778f1..2b9243f 100644 --- a/countdown-generator/index.js +++ b/countdown-generator/index.js @@ -18,29 +18,40 @@ module.exports = { * @param {number} frames * @param {requestCallback} cb - The callback that is run once complete. */ - init: function(time, width=200, height=200, color='ffffff', bg='000000', name='default', frames=30, cb){ + init: function (time, width = 200, height = 200, color = 'ffffff', bg = '000000', name = 'default', imageBgSrc, frames = 30, cb) { // Set some sensible upper / lower bounds this.width = this.clamp(width, 150, 500); this.height = this.clamp(height, 150, 500); this.frames = this.clamp(frames, 1, 90); - + this.bg = '#' + bg; this.textColor = '#' + color; this.name = name; - + // loop optimisations this.halfWidth = Number(this.width / 2); this.halfHeight = Number(this.height / 2); - + this.encoder = new GIFEncoder(this.width, this.height); this.canvas = new Canvas(this.width, this.height); this.ctx = this.canvas.getContext('2d'); - + + var $this = this; + // calculate the time difference (if any) let timeResult = this.time(time); - + // start the gif encoder - this.encode(timeResult, cb); + if (imageBgSrc !== null) { + fs.readFile(imageBgSrc, function (err, image) { + if (err) throw err; + var img = new Canvas.Image; + img.src = image; + $this.encode(timeResult, img, cb); + }); + } else { + this.encode(timeResult, null, cb); + } }, /** * Limit a value between a min / max @@ -50,7 +61,7 @@ module.exports = { * @param max - maximum value number can have * @returns {number} */ - clamp: function(number, min, max){ + clamp: function (number, min, max) { return Math.max(min, Math.min(number, max)); }, /** @@ -62,12 +73,12 @@ module.exports = { // grab the current and target time let target = moment(timeString); let current = moment(); - + // difference between the 2 (in ms) let difference = target.diff(current); - + // either the date has passed, or we have a difference - if(difference <= 0){ + if (difference <= 0) { return 'Date has passed!'; } else { // duration of the difference @@ -79,37 +90,38 @@ module.exports = { * @param {string|Object} timeResult - either the date passed string, or a valid moment duration object * @param {requestCallback} cb - the callback to be run once complete */ - encode: function(timeResult, cb){ + encode: function (timeResult, image, cb) { let enc = this.encoder; let ctx = this.ctx; let tmpDir = process.cwd() + '/tmp/'; // create the tmp directory if it doesn't exist - if (!fs.existsSync(tmpDir)){ + if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir); } - + let filePath = tmpDir + this.name + '.gif'; - + // pipe the image to the filesystem to be written let imageStream = enc - .createReadStream() - .pipe(fs.createWriteStream(filePath)); + .createReadStream() + .pipe(fs.createWriteStream(filePath)); // once finised, generate or serve imageStream.on('finish', () => { // only execute callback if it is a function typeof cb === 'function' && cb(); }); - + // estimate the font size based on the provided width let fontSize = Math.floor(this.width / 12) + 'px'; - let fontFamily = 'Courier New'; // monospace works slightly better - + let fontFamily = 'Roboto'; // monospace works slightly better + // set the font style ctx.font = [fontSize, fontFamily].join(' '); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; + // start encoding gif with following settings enc.start(); enc.setRepeat(0); @@ -117,51 +129,55 @@ module.exports = { enc.setQuality(10); // if we have a moment duration object - if(typeof timeResult === 'object'){ - for(let i = 0; i < this.frames; i++){ + if (typeof timeResult === 'object') { + for (let i = 0; i < this.frames; i++) { // extract the information we need from the duration let days = Math.floor(timeResult.asDays()); let hours = Math.floor(timeResult.asHours() - (days * 24)); let minutes = Math.floor(timeResult.asMinutes()) - (days * 24 * 60) - (hours * 60); let seconds = Math.floor(timeResult.asSeconds()) - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60); - + // make sure we have at least 2 characters in the string days = (days.toString().length == 1) ? '0' + days : days; hours = (hours.toString().length == 1) ? '0' + hours : hours; minutes = (minutes.toString().length == 1) ? '0' + minutes : minutes; seconds = (seconds.toString().length == 1) ? '0' + seconds : seconds; - + // build the date string let string = [days, 'd ', hours, 'h ', minutes, 'm ', seconds, 's'].join(''); - + // paint BG ctx.fillStyle = this.bg; ctx.fillRect(0, 0, this.width, this.height); - + if (image !== null) { + ctx.drawImage(image, 0, 0, this.width, this.height) + } // paint text ctx.fillStyle = this.textColor; ctx.fillText(string, this.halfWidth, this.halfHeight); - + // add finalised frame to the gif enc.addFrame(ctx); - + // remove a second for the next loop timeResult.subtract(1, 'seconds'); } } else { // Date has passed so only using a string - + // BG ctx.fillStyle = this.bg; ctx.fillRect(0, 0, this.width, this.height); - + // Text ctx.fillStyle = this.textColor; ctx.fillText(timeResult, this.halfWidth, this.halfHeight); enc.addFrame(ctx); } - - // finish the gif + enc.finish(); + + // finish the gif + } }; diff --git a/images/bg-image.jpg b/images/bg-image.jpg new file mode 100644 index 0000000..f3fbee4 Binary files /dev/null and b/images/bg-image.jpg differ diff --git a/images/ex1.gif b/images/ex1.gif new file mode 100644 index 0000000..13522db Binary files /dev/null and b/images/ex1.gif differ diff --git a/package.json b/package.json index 63c01db..a0295be 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "author": "Matt Hobbs", "license": "ISC", "dependencies": { - "canvas": "^1.3.16", - "express": "^4.13.4", - "gifencoder": "^1.0.6", - "moment": "^2.13.0" + "canvas": "1.3.16", + "express": "4.13.4", + "gifencoder": "1.0.6", + "moment": "2.13.0" }, "engines": { "node": "6.0.0" diff --git a/public/index.html b/public/index.html index 131e671..04d7cfe 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,7 @@ +