Bläddra i källkod

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
Jonas Geiler 8 månader sedan
förälder
incheckning
7d80ac3c93
1 ändrade filer med 66 tillägg och 32 borttagningar
  1. 66 32
      resources/assets/js/components/ComposeModal.vue

+ 66 - 32
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() {