-
Notifications
You must be signed in to change notification settings - Fork 2
/
renderer-js-inscription-id.js
109 lines (103 loc) · 3.89 KB
/
renderer-js-inscription-id.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* Produces self-contained SVG images, which will preserve the scaling ability of any child SVG
* images, that can be opened in a new tab or downloaded.
*/
async function render(size, ...inscriptionIds) {
try {
/**
* All images must be converted to base64 so that they can be self contained within the
* final combined image. This is needed because SVGs loaded via the "src" attribute in an
* <img> element (which is how the final combined image is loaded) do not load nested images
* from over the network.
*/
const base64Images = await Promise.all(
inscriptionIds.map(async (id) => {
const image = await fetch(`/bitgen-example/content/${id}`);
return await getBase64(await image.blob());
}),
);
const finalImageUrl = generateCombinedImageUrl(size, base64Images, false);
/**
* This fixes a bug in some browsers where resized images with transparent backgrounds have
* transparency edge render artifacts, like grey borders. As long as this image gets 1 pixel
* rendered on screen for 1 frame, the artifacts will go away.
*/
const resizeArtifactFix = /* HTML */ `
<img
class="full-size"
onload="this.remove()"
src="${generateCombinedImageUrl(size, base64Images, true)}"
/>
`;
return /* HTML */ `
<style>
/*
Remove default browser padding/margin for when this is loaded in an iframe.
*/
body,
html {
margin: 0;
padding: 0;
overflow: hidden;
}
.full-size {
position: absolute;
opacity: 1;
top: calc(100% - 1px);
left: calc(100% - 1px);
}
img {
display: block;
}
</style>
${resizeArtifactFix}
<img src="${finalImageUrl}" />
`;
} catch (error) {
return /* HTML */ `
<p style="color: red;">${error?.message || String(error)}</p>
`;
}
}
function generateCombinedImageUrl(size, base64Images, fullSize) {
/**
* Inner SVGs must be generated so that animated GIFs will render correctly as a standalone tab
* in all browsers. Using background urls on an SVG allows "image-rendering: pixelated" to work
* in all browsers.
*/
const innerImages = base64Images.map((base64Image) => {
return /* HTML */ `
<foreignObject x="0" y="0" width="100%" height="100%">
<svg
${fullSize ? 'class="full-size"' : ''}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${size.width} ${size.height}"
width="${size.width}"
height="${size.height}"
style="image-rendering: pixelated; background: url(${base64Image}) no-repeat ${fullSize
? ''
: 'center/contain'};"
></svg>
</foreignObject>
`;
});
const flattenedSvgCode = /* HTML */ `
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${size.width} ${size.height}"
width="${size.width}"
height="${size.height}"
>
${innerImages.join('')}
</svg>
`;
return URL.createObjectURL(new Blob([flattenedSvgCode], {type: 'image/svg+xml'}));
}
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
}