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
This commit is contained in:
Jonas Geiler 2024-10-20 19:42:11 +02:00
parent 249468d154
commit 7d80ac3c93
No known key found for this signature in database

View file

@ -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() {