I’m currently building a photo app. To add some overlay, I’m using a CANVAS I’m drawing multiple layers of images:
- the taken photo
- some bundled image
- potentially additional images
The issue happens when I try to export the drawn canvas. I either get this error message:
Uncaught SecurityError: Failed to execute toDataURL on HTMLCanvasElement: Tainted canvases may not be exported
If I set the crossOrigin="anonymous"
attribute to the elements, which allegedly resolves my tainted canvas issue, I’ll get this error message:
Access to image at 'http://localhost/_app_file_/data/user/0/my.app.identifier/cache/cpcp_capture_cba1ea01.jpg' from origin 'http://localhost:8101' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I tried to update the config.xml
with a network_security_config
- but that did not help.
The solution to this problem is rather simple and annoying at the same time.
There simply is no way to make this work with web technology alone.
I ended up using:
- HTTP plugin for downloading it from the web
- File Plugin to use it if it’s from local storage
- Custom code to figure out if it’s a local or a web URL
- Generating Data URL from downloaded image
downloadImageToDataUrl(imageUrl: string): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const dataFetchPromise = this.isLocalFile(imageUrl)
? this.loadLocalFileToDataUrl(imageUrl)
: this.loadImagePerHttpToDataUrl (imageUrl);
const imageDataUrl = await dataFetchPromise;
resolve(imageDataUrl);
} catch (error) {
reject(error);
}
});
}
private isLocalFile(imageUrl: string): boolean {
const localStartPatterns = [ '/', 'file://', '.' ];
const sanitizedImageUrl = (imageUrl || '').toLocaleLowerCase();
return localStartPatterns.some(pattern => sanitizedImageUrl.startsWith(pattern));
}
private loadLocalFileToDataUrl(path: string): Promise<string> {
const [ protocol, dirPath ] = this.getDirectory(path);
if (!dirPath) {
return Promise.reject([ 'Path not found', path, protocol, dirPath ]);
}
const isBundledAssetFile = path.startsWith('.');
let filePath = null;
if (isBundledAssetFile) {
filePath = path.substring(2); // remove './'
} else {
const completeDirPath = path.startsWith(protocol)
? protocol + '://' + dirPath
: dirPath;
filePath = path.substr(completeDirPath.length);
if (filePath.startsWith('/')) {
filePath = filePath.substring(1);
}
}
return this.file.readAsDataURL(
protocol + '://' + dirPath,
filePath,
);
}
private loadImagePerHttpToDataUrl(imageUrl: string): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const imageBlob = await HTTP.sendRequest(imageUrl, {
method: 'get',
responseType: 'blob',
});
const imageDataUrl = URL.createObjectURL(imageBlob.data);
resolve(imageDataUrl);
} catch(error) {
reject(error);
}
});
}
May it help the next dev stumbling upon this issue, trying to be clever with web only solutions…
1 Like