"use strict" // modules used for graphic manipulation const jimp = require('jimp'); const imagemin = require('imagemin'); const imageminpngquant = require('imagemin-pngquant'); const SHA1 = require('node-sha1'); const async = require('async'); // Mongoose Model using mongoDB const UserModel = require('../models/user'); const AwardingModel = require('../models/awarding'); const RankModel = require('../models/rank'); // http error code table const codes = require('../routes/http-codes'); // standard input/output file extension const fileExt = '.png'; const resourceDir = __dirname + '/../resource/'; let createSignature = (userId, res, next) => { let loadedImage; let user; if (!String(userId).match(/^[0-9a-fA-F]{24}$/)) { let error = new Error('no valid ID ' + userId); error.status = codes.wrongdatatyperequest; return next(error); } UserModel.findById(userId, ['username', 'rankLvl', 'squadId']) .populate('squadId', ['name', 'fraction']) .exec() .then((resUser) => { user = resUser; let platePath; if (resUser && resUser.squadId && resUser.squadId.fraction === 'BLUFOR') { platePath = __dirname + '/backplate/blufor' + fileExt; } else if (resUser && resUser.squadId && resUser.squadId.fraction === 'OPFOR') { platePath = __dirname + '/backplate/opfor' + fileExt; } // kill process on undefined platePath if (!platePath) { throw new Error('Fraction not defined for user with id ' + userId); } return jimp.read(platePath) }) .then((image) => { loadedImage = image; }).then(() => { return jimp.loadFont(__dirname + '/font/DEVAJU_SANS_19.fnt'); }) .then((font) => { loadedImage.print(font, 128, 8, user.username) }) .then(() => { return jimp.loadFont(__dirname + '/font/DEJAVU_SANS_13.fnt'); }) .then((font) => { loadedImage.print(font, 128, 35, user.squadId.name); return font; }) .then((font) => { let rankH, rankW, rankX, rankY; RankModel.findOne({'level': user.rankLvl, 'fraction': user.squadId.fraction}, (err, result) => { if (err) { return next(err) } if (user.squadId.fraction === 'BLUFOR') { rankW = 25; rankH = 60; rankX = 36; rankY = 34; } else { rankW = 61; rankH = 61; rankX = 19; rankY = 36; } jimp.read(resourceDir + 'rank/' + result._id + fileExt) .then((rankImage) => { rankImage.resize(rankW, rankH); loadedImage .print(font, 128, 55, result.name) .composite(rankImage, rankX, rankY) }) .then(() => { addDecorationsAndSave(userId, loadedImage, res, next); }) }) }) .catch((err) => { next(err); }) }; /** * query decorations according to user id and * add all graphics into given image * * @param userId * @param loadedImage - background image * @param res * @param next */ let addDecorationsAndSave = (userId, loadedImage, res, next) => { const medalW = 21; const medalH = 40; const ribbonW = 53; const ribbonH = 15; let medalPx = 630; let medalPy = 5; let ribbonPx = 598; let ribbonPy = 95; AwardingModel.find({'userId': userId}, ['decorationId', 'date'], {sort: {date: 'asc'}}).populate('decorationId', ['isMedal']) .exec((err, awardings) => { if (err) { return next(err); } if (awardings.length > 0) { // use synchronized call to keep correct order of decorations async.eachSeries(awardings, (award, callback) => { jimp.read(resourceDir + 'decoration/' + award.decorationId._id + fileExt).then((decorationImage) => { if (award.decorationId.isMedal) { decorationImage.resize(medalW, medalH); loadedImage.composite(decorationImage, medalPx, medalPy); medalPx = medalPx - 1 - medalW; if (medalPx <= 300) { medalPy = medalPy + 3 + medalH; medalPx = 621; } } else { decorationImage.resize(ribbonW, ribbonH); loadedImage.composite(decorationImage, ribbonPx, ribbonPy); ribbonPx = ribbonPx - 2 - ribbonW; if (ribbonPx <= 174) { ribbonPy = ribbonPy - 3 - ribbonH; ribbonPx = 598; } } callback(); }) }, (err) => { if (err) { throw err; } compareImagesAndSave(loadedImage, userId, res, next); }); } else { compareImagesAndSave(loadedImage, userId, res, next); } } ) }; let compareImagesAndSave = (generatedImage, userId, res, next) => { return jimp.read(resourceDir + 'signature/big/' + userId + fileExt) .then((oldImage) => { // compare hashes of image map to recognize difference const sig1 = SHA1(generatedImage.bitmap.data); const sig2 = SHA1(oldImage.bitmap.data); if (sig1 !== sig2) { saveJimpImageAndCompress(generatedImage, userId, res, next) } else { res.locals.items = {status: 'nothing to do'}; next(); } }) .catch((err) => { saveJimpImageAndCompress(generatedImage, userId, res, next); }) }; /** * * Write Jimp image to file system * Gets minified after 3 seconds timeout * * @param image - Jimp image * @param userId - user id of signature owner * @param res - express response object * @param next - express next-function */ let saveJimpImageAndCompress = (image, userId, res, next) => { image.write(resourceDir + 'signature/big/' + userId + fileExt); setTimeout(() => { imagemin([resourceDir + 'signature/big/' + userId + fileExt], resourceDir + 'signature/', { plugins: [ imageminpngquant({quality: '65-80'}) ] }).then((files) => { res.locals.items = {status: 'success'}; return next(); }) }, 3000); }; module.exports = createSignature;