mirror of
https://github.com/GuerillaStudio/souvenir.git
synced 2024-11-09 16:31:52 +00:00
refact(encode): rewrite with tasks
This commit is contained in:
parent
b5e48f7566
commit
6e83c88c04
2 changed files with 115 additions and 77 deletions
|
@ -1,76 +1,103 @@
|
||||||
import EventEmitter from 'eventemitter3'
|
import { calcProgress } from '/services/util.js'
|
||||||
import genericPool from 'generic-pool'
|
import { task, do as taskDo, waitAll } from 'folktale/concurrency/task'
|
||||||
import { promisesProgress, calcProgress } from '/services/util.js'
|
|
||||||
|
|
||||||
import { GIF_PALETTE_SIZE } from '/constants.js'
|
import { GIF_PALETTE_SIZE } from '/constants.js'
|
||||||
|
|
||||||
export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, { boomerangEffect }) {
|
export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, { boomerangEffect }, progressCallback) {
|
||||||
const emitter = new EventEmitter()
|
return taskDo(function * () {
|
||||||
|
const indexedColorImageList = yield quantizeColorImageDataList(
|
||||||
|
imageDataList,
|
||||||
|
GIF_PALETTE_SIZE,
|
||||||
|
(value) => progressCallback(calcProgress(0, 0.9, value))
|
||||||
|
)
|
||||||
|
|
||||||
const quantizeColorWorkerPool = genericPool.createPool({
|
return writeBlob(
|
||||||
create: () => new Worker('/services/quantize-color.worker.js'),
|
imageWidth,
|
||||||
destroy: worker => worker.terminate()
|
imageHeight,
|
||||||
}, {
|
indexedColorImageList,
|
||||||
min: 0,
|
delayTime,
|
||||||
max: 2
|
boomerangEffect,
|
||||||
|
(value) => progressCallback(calcProgress(0.9, 1, value))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const indexedColorImagesP = imageDataList
|
function quantizeColorImageDataList (imageDataList, paletteSize, progressCallback) {
|
||||||
.map(async imageData => {
|
let complete = 0
|
||||||
const worker = await quantizeColorWorkerPool.acquire()
|
|
||||||
|
|
||||||
const indexedColorImage = await new Promise((resolve, reject) => {
|
const tasks = imageDataList
|
||||||
worker.onerror = reject
|
.map(imageData => quantizeColorImageData(imageData, paletteSize))
|
||||||
worker.onmessageerror = reject
|
.map(task => {
|
||||||
worker.onmessage = event => {
|
return task.map(x => {
|
||||||
resolve(event.data)
|
progressCallback(++complete / imageDataList.length)
|
||||||
}
|
return x
|
||||||
|
|
||||||
worker.postMessage({
|
|
||||||
imageData,
|
|
||||||
paletteSize: GIF_PALETTE_SIZE
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await quantizeColorWorkerPool.release(worker)
|
|
||||||
return indexedColorImage
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const progressPromises = promisesProgress(indexedColorImagesP, function (value) {
|
return waitAll(tasks)
|
||||||
emitter.emit('progress', calcProgress(0, 0.9, value))
|
}
|
||||||
|
|
||||||
|
function quantizeColorImageData (imageData, paletteSize) {
|
||||||
|
return task((resolver) => {
|
||||||
|
const worker = new Worker('/services/quantize-color.worker.js')
|
||||||
|
|
||||||
|
resolver.cleanup(() => {
|
||||||
|
worker.terminate()
|
||||||
|
})
|
||||||
|
|
||||||
|
worker.onerror = resolver.reject
|
||||||
|
worker.onmessageerror = resolver.reject
|
||||||
|
|
||||||
|
worker.onmessage = event => {
|
||||||
|
resolver.resolve(event.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
imageData,
|
||||||
|
paletteSize
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all(progressPromises).then(indexedColorImages => {
|
function writeBlob (
|
||||||
const writeWorker = new Worker('/services/write-gif.worker.js')
|
imageWidth,
|
||||||
|
imageHeight,
|
||||||
|
indexedColorImages,
|
||||||
|
delayTime,
|
||||||
|
boomerangEffect,
|
||||||
|
onProgress
|
||||||
|
) {
|
||||||
|
return task((resolver) => {
|
||||||
|
const worker = new Worker('/services/write-gif.worker.js')
|
||||||
|
|
||||||
writeWorker.onerror = error => emitter.emit('error', error)
|
resolver.cleanup(() => {
|
||||||
writeWorker.onmessageerror = error => emitter.emit('error', error)
|
worker.terminate()
|
||||||
|
})
|
||||||
|
|
||||||
writeWorker.onmessage = event => {
|
worker.onerror = resolver.reject
|
||||||
|
worker.onmessageerror = resolver.reject
|
||||||
|
|
||||||
|
worker.onmessage = event => {
|
||||||
const { type, payload } = event.data
|
const { type, payload } = event.data
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
default:
|
|
||||||
emitter.emit('error', new Error(`Unexpected worker message with type ${type}`))
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'progress':
|
case 'progress':
|
||||||
emitter.emit('progress', calcProgress(0.9, 1, payload.value))
|
onProgress(payload.value)
|
||||||
break
|
return
|
||||||
|
|
||||||
case 'done':
|
case 'done':
|
||||||
const byteArray = new Uint8Array(payload.buffer)
|
resolver.resolve({
|
||||||
const blob = new Blob([byteArray], { type: 'image/gif' })
|
blob: new Blob([new Uint8Array(payload.buffer)], { type: 'image/gif' }),
|
||||||
|
|
||||||
emitter.emit('done', {
|
|
||||||
blob,
|
|
||||||
createdAt: new Date()
|
createdAt: new Date()
|
||||||
})
|
})
|
||||||
break
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
resolver.reject(new Error(`Unexpected worker message with type ${type}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeWorker.postMessage({
|
worker.postMessage({
|
||||||
imageWidth,
|
imageWidth,
|
||||||
imageHeight,
|
imageHeight,
|
||||||
indexedColorImages,
|
indexedColorImages,
|
||||||
|
@ -78,7 +105,4 @@ export function encode ({ imageDataList, imageWidth, imageHeight, delayTime }, {
|
||||||
boomerangEffect
|
boomerangEffect
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(error => emitter.emit('error', error))
|
|
||||||
|
|
||||||
return emitter
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export default {
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
encoding: false,
|
encoding: false,
|
||||||
|
encodingExecution: null,
|
||||||
encodingProgress: 0,
|
encodingProgress: 0,
|
||||||
objectUrl: null,
|
objectUrl: null,
|
||||||
downloadReady: false
|
downloadReady: false
|
||||||
|
@ -54,6 +55,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
back () {
|
back () {
|
||||||
|
this.cancelEncode()
|
||||||
this.$router.push({ name: 'preview' })
|
this.$router.push({ name: 'preview' })
|
||||||
},
|
},
|
||||||
backHome () {
|
backHome () {
|
||||||
|
@ -66,47 +68,59 @@ export default {
|
||||||
URL.revokeObjectURL(this.objectUrl)
|
URL.revokeObjectURL(this.objectUrl)
|
||||||
this.$store.commit('updateGif', null)
|
this.$store.commit('updateGif', null)
|
||||||
},
|
},
|
||||||
startEncoding () {
|
startEncode () {
|
||||||
this.encoding = true
|
this.encoding = true
|
||||||
const encoding = encode(this.capture, { boomerangEffect: this.boomerang })
|
this.encodingProgress = 0
|
||||||
|
|
||||||
encoding.once('error', error => {
|
this.encodingExecution = encode(
|
||||||
console.error(error)
|
this.capture,
|
||||||
|
{ boomerangEffect: this.boomerang },
|
||||||
|
(value) => {
|
||||||
|
this.encodingProgress = value
|
||||||
|
}
|
||||||
|
).run()
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
this.encoding = false
|
this.encoding = false
|
||||||
this.encodingProgress = 0
|
this.encodingProgress = 0
|
||||||
})
|
this.encodingExecution = null
|
||||||
|
}
|
||||||
|
|
||||||
encoding.on('progress', value => {
|
this.encodingExecution.listen({
|
||||||
this.encodingProgress = value
|
onCancelled: cleanup,
|
||||||
})
|
onRejected: cleanup,
|
||||||
|
onResolved: (gif) => {
|
||||||
|
cleanup()
|
||||||
|
this.$store.commit('updateGif', gif)
|
||||||
|
this.fillGIF()
|
||||||
|
this.downloadReady = true
|
||||||
|
|
||||||
encoding.once('done', gif => {
|
if (document.hidden && ('Notification' in window) && Notification.permission === 'granted') {
|
||||||
this.encoding = false
|
const notification = new Notification('You can now download your souvenir', {
|
||||||
this.encodingProgress = 0
|
body: 'Thank you for your patience.',
|
||||||
this.$store.commit('updateGif', gif)
|
icon: appLogo
|
||||||
this.fillGIF()
|
})
|
||||||
this.downloadReady = true
|
|
||||||
|
|
||||||
if (document.hidden && ('Notification' in window) && Notification.permission === 'granted') {
|
notification.addEventListener('click', () => {
|
||||||
const notification = new Notification('You can now download your souvenir', {
|
parent.focus()
|
||||||
body: 'Thank you for your patience.',
|
}, {
|
||||||
icon: appLogo
|
once: true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
notification.addEventListener('click', () => {
|
|
||||||
parent.focus()
|
|
||||||
}, {
|
|
||||||
once: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
cancelEncode () {
|
||||||
|
if (this.encodingExecution) {
|
||||||
|
this.encodingExecution.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
if (!this.capture) {
|
if (!this.capture) {
|
||||||
this.$router.push({ name: 'home' })
|
this.$router.push({ name: 'home' })
|
||||||
} else if (!this.gif) {
|
} else if (!this.gif) {
|
||||||
this.startEncoding()
|
this.startEncode()
|
||||||
} else {
|
} else {
|
||||||
this.fillGIF()
|
this.fillGIF()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue