浏览代码

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 月之前
父节点
当前提交
7d80ac3c93
共有 1 个文件被更改,包括 66 次插入32 次删除
  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() {