Problem Description: I'm trying to create an SVG containing an image fetched from a URL (in this case, from Unsplash) and provide a download button to download the SVG as an image (PNG). However, after downloading the SVG as an image, the embedded image is not visible in the downloaded PNG file.
Code Example: Here is a simplified version of the code I'm using:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Random Image SVG with Download Button</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<svg id="svg-container" width="400" height="200"></svg>
<br>
<button onclick="downloadSvg()">Download as Image</button>
<script>
async function getRandomImageUrl() {
const response = await fetch('https://source.unsplash.com/random');
const imageUrl = response.url;
return imageUrl;
}
async function createRandomImageSVG() {
const imageUrl = await getRandomImageUrl();
const svg = d3.select('#svg-container');
// Append the image directly to the SVG
svg.append('image')
.attr('x', 0)
.attr('y', 0)
.attr('width', 400)
.attr('height', 200)
.attr('href', imageUrl); // Use href instead of xlink:href for newer browsers
}
createRandomImageSVG();
function downloadSvg() {
const svgElement = document.getElementById('svg-container');
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svgElement);
// Create a temporary canvas
const canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 200;
const context = canvas.getContext('2d');
// Create a new image
const img = new Image();
img.onload = function() {
// Draw SVG onto the canvas
context.drawImage(img, 0, 0);
// Convert the canvas to data URL
const dataUrl = canvas.toDataURL('image/png');
// Create a temporary link element
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'random_image.png';
a.click();
};
// Encode the SVG string to Base64 and set as source of image element
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
}
</script>
</body>
</html>
Expected Behavior: The SVG should be converted to a PNG image when the "Download as Image" button is clicked, and the downloaded image should include the random image fetched from Unsplash.
Actual Behavior: After downloading the SVG as an image, the PNG file does not display the embedded image from Unsplash. It appears without the image.
Additional Notes: This issue occurs because the SVG image is referenced from an external URL (Unsplash), and browsers prevent cross-origin requests for security reasons when converting SVG to a data URL. I have tried different methods, including embedding the image directly into the SVG and drawing the SVG onto a canvas before converting it to a data URL, but the issue persists. Question: How can I ensure that the downloaded PNG file includes the embedded image from Unsplash? Is there a way to handle the cross-origin issue effectively when converting SVG to a data URL?
I've found one solution and it's work for me
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Random Image SVG with Download Button</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<svg id="svg-container" width="400" height="300" xmlns="http://www.w3.org/2000/svg"></svg>
<br>
<button onclick="downloadSvg()">Download as Image</button>
<script>
async function getRandomImageUrl() {
const response = await fetch('https://source.unsplash.com/random/400x300');
const blob = await response.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
async function createRandomImageSVG() {
const imageUrl = await getRandomImageUrl();
const svg = d3.select('#svg-container');
svg.append('image')
.attr('x', 0)
.attr('y', 0)
.attr('width', 400)
.attr('height', 300)
.attr('href', imageUrl); // Embed Base64-encoded image data directly
}
createRandomImageSVG();
function downloadSvg() {
const svgElement = document.getElementById('svg-container');
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svgElement);
const canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 300;
const context = canvas.getContext('2d');
const img = new Image();
img.onload = function () {
context.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'random_image.png';
a.click();
};
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
}
</script>
</body>
</html>