From 6d20c85e913fb6c561188f8efa6e1a6af2bbe2bc Mon Sep 17 00:00:00 2001 From: Jonas Geiler Date: Sun, 20 Oct 2024 13:19:41 +0200 Subject: [PATCH 1/4] Fix `applyFilterToMediaSave` passing options to `image.addEventListener` instead of `canvas.toBlob` --- resources/assets/js/components/ComposeModal.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index e4ae59c8..a140ed9e 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -1813,8 +1813,8 @@ export default { self.updateFilteringMedia(); }).catch(err => { }); - }); - }, media.mime, 0.9); + }, media.mime, 0.9); + }); ctx.clearRect(0, 0, image.width, image.height); }, From 249468d1545a061f6b2cc7a127ce61dfefc5a487 Mon Sep 17 00:00:00 2001 From: Jonas Geiler Date: Sun, 20 Oct 2024 13:23:37 +0200 Subject: [PATCH 2/4] Fix `defineErrorMessage` which is not actually using the `msg` variable it creates --- resources/assets/js/components/ComposeModal.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index a140ed9e..3994d79d 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -1095,11 +1095,12 @@ export default { }, defineErrorMessage(errObject) { + let msg; if (errObject.response) { - let msg = errObject.response.data.message ? errObject.response.data.message : 'An unexpected error occured.'; + msg = errObject.response.data.message ? errObject.response.data.message : 'An unexpected error occured.'; } else { - let msg = errObject.message; + msg = errObject.message; } return swal('Oops, something went wrong!', msg, 'error'); }, From 7d80ac3c93d58cfce714feb8ab02dc0e2befa233 Mon Sep 17 00:00:00 2001 From: Jonas Geiler Date: Sun, 20 Oct 2024 19:42:11 +0200 Subject: [PATCH 3/4] Improve media filtering by using OffscreenCanvas, if supported Also improve browser support by testing for each feature instead of checking the user agent. Also improve error handling by using promises. Fixes #2939 Fixes #4194 --- .../assets/js/components/ComposeModal.vue | 98 +++++++++++++------ 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 3994d79d..6036185f 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() { From e318d526c77287aec9fb89c239779ceb799dceb5 Mon Sep 17 00:00:00 2001 From: Jonas Geiler Date: Mon, 21 Oct 2024 09:44:49 +0200 Subject: [PATCH 4/4] Add missing `reject` argument --- resources/assets/js/components/ComposeModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 6036185f..94f6f5e1 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -1831,7 +1831,7 @@ export default { quality: 1, }); } else { - blob = await new Promise(resolve => { + blob = await new Promise((resolve, reject) => { canvas.toBlob(blob => { if(blob) { resolve(blob);