Skip to content

Commit

Permalink
Animated GIF exporting!
Browse files Browse the repository at this point in the history
  • Loading branch information
setpixel committed Jun 2, 2017
1 parent 5b4e3c0 commit b40ae98
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"electron-is-dev": "0.1.2",
"electron-localshortcut": "^1.0.0",
"express": "^4.15.2",
"gifencoder": "^1.0.6",
"gl-vec2": "^1.0.0",
"moment": "^2.18.1",
"socket.io": "^1.7.3",
Expand Down
4 changes: 4 additions & 0 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,4 +616,8 @@ ipcMain.on('toggleNewShot', (event, arg) => {

ipcMain.on('showTip', (event, arg) => {
mainWindow.webContents.send('showTip', arg)
})

ipcMain.on('exportAnimatedGif', (event, arg) => {
mainWindow.webContents.send('exportAnimatedGif', arg)
})
10 changes: 10 additions & 0 deletions src/js/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ const template = [
{
type: 'separator'
},
{
label: 'Export Animated GIF',
accelerator: 'CmdOrCtrl+E',
click ( item, focusedWindow, event) {
ipcRenderer.send('exportAnimatedGif')
}
},
{
type: 'separator'
},
{
accelerator: 'CmdOrCtrl+P',
label: 'Print current scene worksheet...',
Expand Down
102 changes: 102 additions & 0 deletions src/js/window/exporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const EventEmitter = require('events').EventEmitter
const fs = require('fs')
const path = require('path')
const util = require('../utils/index.js')
const GIFEncoder = require('gifencoder')
const moment = require('moment')

const getImage = (url) => {
return new Promise(function(resolve, reject){
let img = new Image()
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(img)
}
img.src = url
})
}

class Exporter extends EventEmitter {
constructor () {
super()
}

exportAnimatedGif (boards, boardSize, destWidth, boardPath) {
let canvases = []

let sequence = Promise.resolve()
boards.forEach((board)=> {
// Chain one computation onto the sequence
let canvas = document.createElement('canvas')
canvas.width = boardSize.width
canvas.height = boardSize.height
let context = canvas.getContext('2d')
sequence = sequence.then(function() {
if (board.layers) {
// get reference layer if exists
if (board.layers['reference']) {
let filepath = path.join(boardPath, 'images', board.layers['reference'].url)
return getImage(filepath)
}
}
}).then(function(result) {
// Draw reference if exists and load main.
if (result) {
context.drawImage(result,0,0)
}
let filepath = path.join(boardPath, 'images', board.url)
return getImage(filepath)
}).then(function(result) {
// draw main and push it to the array of canvases
if (result) {
context.drawImage(result,0,0)
}
canvases.push(canvas)
})
})

sequence.then(()=>{
let aspect = boardSize.height / boardSize.width
let destSize = {width: destWidth, height: Math.floor(destWidth*aspect)}
let encoder = new GIFEncoder(destSize.width, destSize.height)
// save in the boards directory
let filename = boardPath.split(path.sep)
filename = filename[filename.length-1]
if (!fs.existsSync(path.join(boardPath, 'exports'))) {
fs.mkdirSync(path.join(boardPath, 'exports'))
}
let filepath = path.join(boardPath, 'exports', filename + ' ' + moment().format('YYYY-MM-DD hh.mm.ss') + '.gif')
console.log(filepath)
encoder.createReadStream().pipe(fs.createWriteStream(filepath))
encoder.start()
encoder.setRepeat(0) // 0 for repeat, -1 for no-repeat
encoder.setDelay(2000) // frame delay in ms
encoder.setQuality(10) // image quality. 10 is default.
let canvas = document.createElement('canvas')
canvas.width = destSize.width
canvas.height = destSize.height
let context = canvas.getContext('2d')
for (var i = 0; i < boards.length; i++) {
context.fillStyle = 'white'
context.fillRect(0,0,destSize.width,destSize.height)
context.drawImage(canvases[i], 0,0,destSize.width,destSize.height)
let duration
if (boards[i].duration) {
duration = boards[i].duration
} else {
duration = 2000
}
encoder.setDelay(duration)
encoder.addFrame(context)
}
encoder.finish()
// emit a finish event!
this.emit('complete', filepath)
})
}

}

module.exports = new Exporter()
35 changes: 34 additions & 1 deletion src/js/window/main-window.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const LayersEditor = require('./layers-editor.js')
const sfx = require('../wonderunit-sound.js')
const keytracker = require('../utils/keytracker.js')
const storyTips = new(require('./story-tips'))(sfx, notifications)
const exporter = require('./exporter.js')

const pkg = require('../../../package.json')

Expand Down Expand Up @@ -2665,6 +2666,32 @@ let copyBoards = () => {
}
}

let exportAnimatedGif = () => {
// load all the images in the selection
if (selections.has(currentBoard)) {
saveImageFile()
}
let boards
if (selections.size == 1) {
boards = util.stringifyClone(boardData.boards)
} else {
boards = [...selections].sort(util.compareNumbers).map(n => util.stringifyClone(boardData.boards[n]))
}
let boardSize = storyboarderSketchPane.sketchPane.getCanvasSize()

notifications.notify({message: "Exporting " + boards.length + " boards. Please wait...", timing: 5})
sfx.down()
setTimeout(()=>{
exporter.exportAnimatedGif(boards, boardSize, 500, boardPath, boardFilename)
}, 1000)
}

exporter.on('complete', path => {
notifications.notify({message: "I exported your board selection as a GIF. Share it with your friends! Post it to you twitter thing or your slack dingus.", timing: 20})
sfx.positive()
shell.showItemInFolder(path)
})

/**
* Paste
*
Expand Down Expand Up @@ -3276,4 +3303,10 @@ ipcRenderer.on('toggleSpeaking', (event, args) => {

ipcRenderer.on('showTip', (event, args) => {
storyTips.show()
})
})

ipcRenderer.on('exportAnimatedGif', (event, args) => {
exportAnimatedGif()
})


2 changes: 2 additions & 0 deletions src/js/window/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const removeNotification = (index) => {
if (notification) {
let el = notification.el
el.style.opacity = 0
el.style.height = '0px'

clearTimeout(notification.index)
if (el.parentNode) {
setTimeout(() => {
Expand Down

0 comments on commit b40ae98

Please sign in to comment.