Last active
December 18, 2017 14:45
-
-
Save richardwillars/e2843119cff74245eab2d162bd1e6483 to your computer and use it in GitHub Desktop.
Thumbnail generator plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Plugin = require('../Plugin') | |
const Utils = require('../../core/Utils') | |
/** | |
* The Thumbnail Generator plugin | |
* | |
*/ | |
module.exports = class ThumbnailGenerator extends Plugin { | |
constructor(core, opts) { | |
super(core, opts) | |
this.type = 'thumbnail' | |
this.id = 'ThumbnailGenerator' | |
this.title = 'Thumbnail Generator' | |
this.queue = []; | |
this.queueProcessing = false; | |
const defaultOptions = { | |
thumbnailSize: 200 | |
} | |
this.opts = Object.assign({}, defaultOptions, opts) | |
} | |
/** | |
* Create a thumbnail for the given Uppy file object. | |
* | |
* @param {{data: Blob}} file | |
* @param {number} width | |
* @return {Promise} | |
*/ | |
createThumbnail (file, targetWidth) { | |
const originalUrl = URL.createObjectURL(file.data) | |
const onload = new Promise((resolve, reject) => { | |
const image = new Image() | |
image.src = originalUrl | |
image.onload = () => { | |
URL.revokeObjectURL(originalUrl) | |
resolve(image) | |
} | |
image.onerror = () => { | |
// The onerror event is totally useless unfortunately, as far as I know | |
URL.revokeObjectURL(originalUrl) | |
reject(new Error('Could not create thumbnail')) | |
} | |
}) | |
return onload.then((image) => { | |
const targetHeight = this.getProportionalHeight(image, targetWidth) | |
const canvas = this.resizeImage(image, targetWidth, targetHeight) | |
return this.canvasToBlob(canvas, 'image/png') | |
}).then((blob) => { | |
return URL.createObjectURL(blob) | |
}) | |
} | |
/** | |
* Resize an image to the target `width` and `height`. | |
* | |
* Returns a Canvas with the resized image on it. | |
*/ | |
resizeImage (image, targetWidth, targetHeight) { | |
let sourceWidth = image.width | |
let sourceHeight = image.height | |
if (targetHeight < image.height / 2) { | |
const steps = Math.floor(Math.log(image.width / targetWidth) / Math.log(2)) | |
const stepScaled = this.downScaleInSteps(image, steps) | |
image = stepScaled.image | |
sourceWidth = stepScaled.sourceWidth | |
sourceHeight = stepScaled.sourceHeight | |
} | |
const canvas = document.createElement('canvas') | |
canvas.width = targetWidth | |
canvas.height = targetHeight | |
const context = canvas.getContext('2d') | |
context.drawImage(image, | |
0, 0, sourceWidth, sourceHeight, | |
0, 0, targetWidth, targetHeight) | |
return canvas | |
} | |
/** | |
* Downscale an image by 50% `steps` times. | |
*/ | |
downScaleInSteps (image, steps) { | |
let source = image | |
let currentWidth = source.width | |
let currentHeight = source.height | |
for (let i = 0; i < steps; i += 1) { | |
const canvas = document.createElement('canvas') | |
const context = canvas.getContext('2d') | |
canvas.width = currentWidth / 2 | |
canvas.height = currentHeight / 2 | |
context.drawImage(source, | |
// The entire source image. We pass width and height here, | |
// because we reuse this canvas, and should only scale down | |
// the part of the canvas that contains the previous scale step. | |
0, 0, currentWidth, currentHeight, | |
// Draw to 50% size | |
0, 0, currentWidth / 2, currentHeight / 2) | |
currentWidth /= 2 | |
currentHeight /= 2 | |
source = canvas | |
} | |
return { | |
image: source, | |
sourceWidth: currentWidth, | |
sourceHeight: currentHeight | |
} | |
} | |
/** | |
* Save a <canvas> element's content to a Blob object. | |
* | |
* @param {HTMLCanvasElement} canvas | |
* @return {Promise} | |
*/ | |
canvasToBlob (canvas, type, quality) { | |
if (canvas.toBlob) { | |
return new Promise((resolve) => { | |
canvas.toBlob(resolve, type, quality) | |
}) | |
} | |
return Promise.resolve().then(() => { | |
return utils.dataURItoBlob(canvas.toDataURL(type, quality), {}) | |
}) | |
} | |
getProportionalHeight (img, width) { | |
const aspect = img.width / img.height | |
return Math.round(width / aspect) | |
} | |
/** | |
* Set the preview URL for a file. | |
*/ | |
setPreviewURL (fileID, preview) { | |
const { files } = this.core.state | |
this.core.setState({ | |
files: Object.assign({}, files, { | |
[fileID]: Object.assign({}, files[fileID], { | |
preview: preview | |
}) | |
}) | |
}) | |
} | |
addToQueue (item) { | |
this.queue.push(item); | |
if (this.queueProcessing === false) { | |
this.processQueue(); | |
} | |
} | |
processQueue() { | |
this.queueProcessing = true; | |
if (this.queue.length > 0) { | |
const current = this.queue.shift(); | |
return this.requestThumbnail(current) | |
.catch((err) => { | |
}) | |
.then(() => this.processQueue()); | |
} | |
else { | |
this.queueProcessing = false; | |
} | |
} | |
requestThumbnail(file) { | |
if (Utils.isPreviewSupported(file.type) && !file.isRemote) { | |
return this.createThumbnail(file, this.opts.thumbnailSize).then((preview) => { | |
this.setPreviewURL(file.id, preview) | |
}).catch((err) => { | |
console.warn(err.stack || err.message) | |
}) | |
} | |
return Promise.resolve(); | |
} | |
install() { | |
this.core.on('core:file-added', (file) => { | |
this.addToQueue(file); | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment