vigour-shutter
Version:
Makes a sprite out of an array of urls
698 lines (678 loc) • 20.3 kB
JavaScript
var spawn = require('child_process').spawn
var vspawn = require('vigour-spawn')
var path = require('path')
var Promise = require('promise')
var cwd = process.cwd()
var opts
var pids = {}
var limits = [
'-limit Area 134217728',
'-limit Disk 1GB',
'-limit Map 128',
'-limit time 7',
'-limit thread 2'
].join(' ')
module.exports = exports = {}
exports.init = function (_options) {
opts = _options
}
exports.computeDimensions = function (subject, dimensions) {
return vspawn(opts.identifyPath.val + ' ' + limits + ' -format %wx%h ' + subject, { getOutput: true })
.then((str) => {
var parts = str.split('x')
var w = parseInt(parts[0], 10)
var h = parseInt(parts[1], 10)
if (!dimensions.width && !dimensions.height) {
dimensions.width = w
dimensions.height = h
} else {
var ratio = w / h
if (!dimensions.width) {
dimensions.width = '' + Math.round(ratio * parseInt(dimensions.height, 10))
}
if (!dimensions.height) {
dimensions.height = '' + Math.round(parseInt(dimensions.width, 10) / ratio)
}
}
return dimensions
})
}
exports.effect = function (query, subject, dimensions, out) {
var prom = Promise.resolve()
if (!dimensions.width || !dimensions.height) {
prom = prom.then(() => {
return exports.computeDimensions(subject, dimensions)
})
} else {
prom = prom.then(() => {
return dimensions
})
}
prom = prom.then((targetDimensions) => {
return new Promise(function (resolve, reject) {
var effect = (query.effect) ? query.effect : 'smartResize'
if (opts.verbose.val) {
console.log('query', query)
}
if (!query.outType && query.url && query.url.slice(-4).toLowerCase() === '.png' && ['mask', 'blur', 'overlayBlur'].indexOf(effect) === -1) {
query.outType = 'png'
}
var format = (query.outType)
? ' -format ' + query.outType.toUpperCase()
: ''
try {
resolve(exports.effects[effect](subject, query, targetDimensions, format, out))
} catch (e) {
e.message += ': ' + opts.invalidRequestMessage.val
e.effect = effect
reject(e)
}
})
})
return prom
}
exports.effects = {}
exports.effects.smartResize = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var newOut = out + '.' + ((options.outType) ? options.outType : 'jpg')
var error
if (subject && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits + ' ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0' +
format +
' ' + newOut
, function (err) {
if (err) {
reject(err)
} else {
resolve(newOut)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'smartResize'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.effects.smartResize2 = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits + ' ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'smartResize2'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.effects.mask = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.mask && options.fillColor) {
resolve(exports.mask(subject
, path.join(__dirname, '..', 'images', options.mask + '.png')
, '#' + options.fillColor
, dimensions
, format
, out + '.' + ((options.outType) ? options.outType : 'jpg')))
} else {
error = new Error('Invalid request')
error.fn = 'mask'
error.details = 'invalid options'
error.parameters = {
mask: options.mask || 'MISSING',
fillColor: options.fillColor || 'MISSING'
}
reject(error)
}
})
}
exports.effects.tMask = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.mask) {
resolve(exports.tMask(subject
, path.join(__dirname, '..', 'images', options.mask + '.png')
, dimensions
, format
, out + '.' + ((options.outType) ? options.outType : 'png')))
} else {
error = new Error('Invalid request')
error.fn = 'tMask'
error.details = 'invalid options'
error.parameters = {
mask: options.mask || 'MISSING'
}
reject(error)
}
})
}
exports.effects.overlay = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.overlay) {
resolve(exports.overlay(subject
, path.join(__dirname, '..', 'images', options.overlay + '.png')
, dimensions
, format
, out + '.' + ((options.outType) ? options.outType : 'jpg')))
} else {
error = new Error('Invalid request')
error.fn = 'overlay'
error.details = 'invalid options'
error.parameters = {
overlay: options.overlay || 'MISSING'
}
reject(error)
}
})
}
exports.effects.composite = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.overlay) {
resolve(exports.composite(subject
, path.join(__dirname, '..', 'images', options.overlay + '.png')
, dimensions
, format
, out + '.' + ((options.outType) ? options.outType : 'png')))
} else {
error = new Error('Invalid request')
error.fn = 'composite'
error.details = 'invalid options'
error.parameters = {
overlay: options.overlay || 'MISSING'
}
reject(error)
}
})
}
exports.effects.blur = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.radius && options.sigma) {
resolve(exports.blur(subject
, options.radius
, options.sigma
, dimensions
, format
, out + '.' + ((options.outType) ? options.outType : 'jpg')))
} else {
error = new Error('Invalid request')
error.fn = 'blur'
error.details = 'invalid options'
error.parameters = {
radius: options.radius || 'MISSING',
sigma: options.sigma || 'MISSING'
}
reject(error)
}
})
}
exports.effects.overlayBlur = function (subject, options, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var error
if (options.overlay && options.radius && options.sigma) {
resolve(exports.overlayBlur(subject,
path.join(__dirname, '..', 'images', options.overlay + '.png'),
options.radius,
options.sigma,
dimensions,
format,
out + '.' + ((options.outType) ? options.outType : 'jpg')))
} else {
error = new Error('Invalid request')
error.fn = 'overlayBlur'
error.details = 'invalid options'
error.parameters = {
overlay: options.overlay || 'MISSING',
radius: options.radius || 'MISSING',
sigma: options.sigma || 'MISSING'
}
reject(error)
}
})
}
exports.overlayBlur = function (subject, overlay, radius, sigma, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && overlay && radius && sigma && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits +
' ( ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0' +
' -blur ' + radius + 'x' + sigma + ' )' +
' ( ' + overlay +
' -resize ' + dimensionsString + '! )' +
' -composite' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'overlayBlur'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
overlay: overlay || 'MISSING',
radius: radius || 'MISSING',
sigma: sigma || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.overlay = function (subject, overlay, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && overlay && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits +
' ( ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0 )' +
' ( ' + overlay +
' -resize ' + dimensionsString + '! )' +
' -composite' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'overlay'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
overlay: overlay || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.blur = function (subject, radius, sigma, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && radius && sigma && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits + ' ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0' +
' -blur ' + radius + 'x' + sigma +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'blur'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
radius: radius || 'MISSING',
sigma: sigma || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.darken = function (subject, color, factor, format, out) {
return new Promise(function (resolve, reject) {
var error
if (subject && color && factor && out) {
execCommand(opts.convertPath.val + ' ' + limits + ' ' + subject +
' -fill ' + color +
' -colorize ' + factor +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'darken'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
color: color || 'MISSING',
factor: factor || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.mask = function (subject, mask, color, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && mask && color && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits + ' -size ' + dimensionsString +
' xc:' + color +
' ( ' +
' ( ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0 )' +
' ( ' + mask +
' -resize ' + dimensionsString +
' -gravity Center )' +
' -compose CopyOpacity -composite' +
' )' +
' -gravity Center' +
' -geometry +0+0' +
' -compose src-over' +
' -composite ' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'mask'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
mask: mask || 'MISSING',
color: color || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.tMask = function (subject, mask, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && mask && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits +
' ( ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0 )' +
' ( ' + mask +
' -resize ' + dimensionsString +
' -gravity Center )' +
' -compose CopyOpacity -composite' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'tMask'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
mask: mask || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
exports.composite = function (subject, overlay, dimensions, format, out) {
return new Promise(function (resolve, reject) {
var dimensionsString = dimensions.width + 'x' + dimensions.height
var error
if (subject && overlay && dimensions && out) {
execCommand(opts.convertPath.val + ' ' + limits +
' ( ' + subject +
' -resize ' + dimensionsString + '^' +
' -gravity Center' +
' -crop ' + dimensionsString + '+0+0 )' +
' ( ' + overlay +
' -resize ' + dimensionsString + '!' +
' -gravity Center )' +
' -compose CopyOpacity -composite' +
format +
' ' + out
, function (err) {
if (err) {
reject(err)
} else {
resolve(out)
}
})
} else {
error = new Error('Invalid request')
error.fn = 'composite'
error.details = 'invalid parameters'
error.parameters = {
subject: subject || 'MISSING',
overlay: overlay || 'MISSING',
dimensions: dimensions || 'MISSING',
out: out || 'MISSING'
}
reject(error)
}
})
}
// exports.sprite = function (images, dimensions, tmpDir, maxCols, format, out) {
// return new Promise(function (resolve, reject) {
// var l
// , nbLeft
// , i
// , subSprites
// , stop
// , spritePath
// , subSpritePath
// , error
// if (images && dimensions && tmpDir && maxCols) {
// l = images.length
// nbLeft = Math.ceil(l / maxCols)
// subSprites = []
// spritePath = out + '.jpg'
// if (l > maxCols) {
// for (i = 0; i < l; i += maxCols) {
// subSpritePath = tmpDir + '/' + i + '.jpg'
// subSprites.push(subSpritePath)
// exports.subSprite(images.slice(i, i + maxCols)
// , dimensions
// , format
// , subSpritePath
// , function (err) {
// if (err) {
// reject(err)
// } else {
// nbLeft -= 1
// if (nbLeft === 0) {
// resolve(exports.assembleSprites(subSprites, spritePath))
// }
// }
// })
// }
// } else {
// resolve(exports.subSprite(images, dimensions, spritePath))
// }
// } else {
// error = new Error("Invalid request")
// error.fn = "sprite"
// error.details = "invalid parameters"
// error.parameters = {
// images: images || "MISSING"
// , dimensions: dimensions || "MISSING"
// , tmpDir: tmpDir || "MISSING"
// , maxCols: maxCols || "MISSING"
// }
// reject(error)
// }
// })
// }
// exports.subSprite = function (images, dimensions, format, out) {
// return new Promise(function (resolve, reject) {
// var dimensionsString = dimensions.width + "x" + dimensions.height
// , imagesString = "'" + images.join("' '") + "'"
// , error
// if (images && dimensions && out) {
// execCommand("gm convert " + imagesString
// + " -resize '" + dimensionsString + "^'"
// + " -gravity 'Center'"
// + " -crop '" + dimensionsString + "+0+0'"
// + " +append"
// + format
// + " '" + out + "'"
// , function (err) {
// if (err) {
// reject(err)
// } else {
// resolve(out)
// }
// })
// } else {
// error = new Error("Invalid request")
// error.fn = "subSprite"
// error.details = "invalid parameters"
// error.parameters = {
// images: images || "MISSING"
// , dimensions: dimensions || "MISSING"
// , out: out || "MISSING"
// }
// reject(error)
// }
// })
// }
// exports.assembleSprites = function (images, out) {
// return new Promise(function (resolve, reject) {
// var imagesString = "'" + images.join("' '") + "'"
// , error
// if (images && out) {
// execCommand("gm convert " + imagesString
// + " -append"
// + format
// + " '" + out + "'"
// , function (err) {
// if (err) {
// reject(err)
// } else {
// resole(out)
// }
// })
// } else {
// error = new Error("Invalid request")
// error.fn = "assembleSprites"
// error.details = "invalid parameters"
// error.parameters = {
// images: images || "MISSING"
// , out: out || "MISSING"
// }
// reject(error)
// }
// })
// }
function execCommand (commandStr, cb) {
var args = commandStr.split(' ').filter(function (item) {
return item !== ''
})
var command = args.shift()
if (opts.verbose.val) {
console.log('args', args)
}
var proc = spawn(command
, args
, { cwd: cwd })
pids[proc.pid] = commandStr
var out = ''
var err = ''
proc.stdout.on('data', function (data) {
out += data
})
proc.stderr.on('data', function (data) {
err += data
})
proc.on('close', function (code) {
delete pids[proc.pid]
if (err !== '') {
cb(err)
} else {
cb(null, out)
}
})
}
process.on('SIGINT', function () {
process.exit()
})
process.on('exit', function () {
killAllChildProcesses()
})
function killAllChildProcesses () {
for (var key in pids) {
console.log('killing', pids[key], 'pid', key)
process.kill(key, 'SIGINT')
delete pids[key]
}
}