diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 3994d79d2..6036185f4 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -1766,57 +1766,91 @@ export default { applyFilterToMedia() { // this is where the magic happens - var ua = navigator.userAgent.toLowerCase(); - if(ua.indexOf('firefox') == -1 && ua.indexOf('chrome') == -1) { - this.isPosting = false; - swal('Oops!', 'Your browser does not support the filter feature.', 'error'); - this.page = 3; - return; - } - let count = this.media.filter(m => m.filter_class).length; if(count) { this.page = 'filteringMedia'; this.filteringRemainingCount = count; this.$nextTick(() => { this.isFilteringMedia = true; - this.media.forEach((media, idx) => this.applyFilterToMediaSave(media, idx)); + Promise.all(this.media.map(media => { + return this.applyFilterToMediaSave(media); + })).catch(err => { + console.error(err); + swal('Oops!', 'An error occurred while applying filters to your media. Please refresh the page and try again. If the problem persist, please try a different web browser.', 'error'); + }); }) } else { this.page = 3; } }, - applyFilterToMediaSave(media, idx) { + async applyFilterToMediaSave(media) { if(!media.filter_class) { return; } - let self = this; - let data = null; - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - let image = document.createElement('img'); + // Load image + const image = document.createElement('img'); image.src = media.url; - image.addEventListener('load', e => { + await new Promise((resolve, reject) => { + image.addEventListener('load', () => resolve()); + image.addEventListener('error', () => { + reject(new Error('Failed to load image')); + }); + }); + + // Create canvas + let canvas; + let usingOffscreenCanvas = false; + if('OffscreenCanvas' in window) { + canvas = new OffscreenCanvas(image.width, image.height); + usingOffscreenCanvas = true; + } else { + canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; - ctx.filter = App.util.filterCss[media.filter_class]; - ctx.drawImage(image, 0, 0, image.width, image.height); - ctx.save(); - canvas.toBlob(function(blob) { - data = new FormData(); - data.append('file', blob); - data.append('id', media.id); - axios.post('/api/compose/v0/media/update', data) - .then(res => { - self.media[idx].is_filtered = true; - self.updateFilteringMedia(); - }).catch(err => { - }); - }, media.mime, 0.9); - }); - ctx.clearRect(0, 0, image.width, image.height); + } + + // Draw image with filter to canvas + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Failed to get canvas context'); + } + if (!('filter' in ctx)) { + throw new Error('Canvas filter not supported'); + } + ctx.filter = App.util.filterCss[media.filter_class]; + ctx.drawImage(image, 0, 0, image.width, image.height); + ctx.save(); + + // Convert canvas to blob + let blob; + if(usingOffscreenCanvas) { + blob = await canvas.convertToBlob({ + type: media.mime, + quality: 1, + }); + } else { + blob = await new Promise(resolve => { + canvas.toBlob(blob => { + if(blob) { + resolve(blob); + } else { + reject( + new Error('Failed to convert canvas to blob'), + ); + } + }, media.mime, 1); + }); + } + + // Upload blob / Update media + const data = new FormData(); + data.append('file', blob); + data.append('id', media.id); + await axios.post('/api/compose/v0/media/update', data); + media.is_filtered = true; + this.updateFilteringMedia(); }, updateFilteringMedia() {