Skip to content

Commit

Permalink
Merge pull request #48 from Arzte/push-vmuorrupznqo
Browse files Browse the repository at this point in the history
Don't exceed max canvas size of browser
  • Loading branch information
ltouroumov authored Sep 30, 2024
2 parents b25d9bf + 07d8220 commit 564333b
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 68 deletions.
151 changes: 83 additions & 68 deletions components/viewer/modal/BackpackModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
</template>

<script setup lang="ts">
import canvasSize from 'canvas-size';
import { elementToSVG, inlineResources } from 'dom-to-svg';
import * as R from 'ramda';
import { computed } from 'vue';
Expand Down Expand Up @@ -140,82 +141,96 @@ const objectMode = computed(() => {
else return ViewContext.BackpackEnabled;
});

const maxCanvasSize = async (size: number) => {
const { width, height } = await canvasSize.maxArea({
max: size,
usePromise: true,
});
return { width, height };
};

const backpackRef = ref<HTMLDivElement>();
const isLoading = ref(false);
const backpackToImage = async () => {
if (backpackRef.value && packRows.value.length >= 1) {
isLoading.value = true;
const $toast = useToast();
const toastGenerateImage = $toast.info('Generating image...', {
timeout: false,
});
// Wait for the next tick to ensure DOM is updated before getting the element.
await nextTick();
if (packRows.value.length <= 0) {
alert(
'No objects selected to create a backpack image, select at least one object to create a image.',
);
return;
}
if (!backpackRef.value) return;

// Set background color for svg to project background color if it exists
const currentBackground = backpackRef.value.style.backgroundColor;
backpackRef.value.style.backgroundColor =
project?.data.styling.backgroundColor ?? currentBackground;
// Convert backpack to SVG
const svgDocument = elementToSVG(backpackRef.value);
// Inline external resources (fonts, images, etc) as data: URIs
await inlineResources(svgDocument.documentElement);
// Restore background color
backpackRef.value.style.backgroundColor = currentBackground;
// Get SVG string
const svgString = new XMLSerializer().serializeToString(svgDocument);
// Create a Blob from the SVG string
const svg = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
// Create a downloadable link for img src
const svgUrl = URL.createObjectURL(svg);
const img = new Image();
// set the image src to the URL of the Blob
img.src = svgUrl;
// Wait until the image has loaded
await img.decode();
// Create a canvas to draw the image to
const canvas = document.createElement('canvas');
// Set canvas dimensions to match the image
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d')!;
// Draw the image to the canvas
ctx.drawImage(img, 0, 0);
// Get the image data as a PNG string
const url = canvas.toDataURL('image/png');
// Remove the canvas
canvas.remove();
isLoading.value = true;
const $toast = useToast();
const toastGenerateImage = $toast.info('Generating image...', {
timeout: false,
});
// Wait for the next tick to ensure DOM is updated before getting the element.
await nextTick();

isLoading.value = false;
$toast.dismiss(toastGenerateImage);
// Set background color for svg to project background color if it exists
const currentBackground = backpackRef.value.style.backgroundColor;
backpackRef.value.style.backgroundColor =
project?.data.styling.backgroundColor ?? currentBackground;
// Convert backpack to SVG
const svgDocument = elementToSVG(backpackRef.value);
// Inline external resources (fonts, images, etc) as data: URIs
await inlineResources(svgDocument.documentElement);
// Restore background color
backpackRef.value.style.backgroundColor = currentBackground;
// Get SVG string
const svgString = new XMLSerializer().serializeToString(svgDocument);
// Create a Blob from the SVG string
const svg = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
// Create a downloadable link for img src
const svgUrl = URL.createObjectURL(svg);
const img = new Image();
// set the image src to the URL of the Blob
img.src = svgUrl;
// Wait until the image has loaded
await img.decode();
// Create a canvas to draw the image to
const canvas = document.createElement('canvas');
// Ensure the image size is not too large for the canvas
const { width, height } = await maxCanvasSize(
Math.max(img.naturalWidth, img.naturalHeight),
);
// Set canvas dimensions to match the image
canvas.width = Math.min(width, img.naturalWidth);
canvas.height = Math.min(height, img.naturalHeight);
const ctx = canvas.getContext('2d')!;
// Draw the image to the canvas
ctx.drawImage(img, 0, 0);
// Get the image data as a PNG string
const url = canvas.toDataURL('image/png');
// Remove the canvas
canvas.remove();

// Ensure the URL is valid before trying to download it
if (!url.startsWith('data:image/png')) {
$toast.error('Failed to generate backpack image.');
console.log(url);
} else {
$toast.success('Backpack image generated');
// Create a element to download the image
const element = document.createElement('a');
// Set the download link href and download attribute
element.href = url;
element.download = `backpack-${new Date().toLocaleString()}.png`;
isLoading.value = false;
$toast.dismiss(toastGenerateImage);

// Click the link to download the image
await nextTick(() => {
element.click();
});
// Remove the element once downloaded
element.remove();
}
// Ensure the URL is valid before trying to download it
if (!url.startsWith('data:image/png')) {
$toast.error('Failed to generate backpack image.');
console.log(url);
} else {
$toast.success('Backpack image generated');
// Create a element to download the image
const element = document.createElement('a');
// Set the download link href and download attribute
element.href = url;
element.download = `backpack-${new Date().toLocaleString()}.png`;

// Clean up the URL after download
URL.revokeObjectURL(url);
} else if (packRows.value.length === 0) {
alert(
'No objects selected to create a backpack image, select at least one object to create a image.',
);
// Click the link to download the image
await nextTick(() => {
element.click();
});
// Remove the element once downloaded
element.remove();
}

// Clean up the URL after download
URL.revokeObjectURL(url);
};
</script>

Expand Down Expand Up @@ -266,7 +281,7 @@ const backpackToImage = async () => {
}

.backpackRender {
width: 1280px !important;
width: 2000px !important;
}
.backpackRender .pack-info-container {
position: unset;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@nuxt/eslint-config": "^0.5.6",
"@nuxt/eslint-plugin": "^0.5.6",
"@nuxt/image": "latest",
"@types/canvas-size": "^1.2.2",
"@types/node": "^20.16.3",
"@typescript-eslint/eslint-plugin": "^8.4.0",
"@typescript-eslint/parser": "^8.4.0",
Expand Down Expand Up @@ -43,6 +44,7 @@
"@vueuse/core": "^11.0.3",
"@vueuse/nuxt": "^11.0.3",
"bootstrap": "5.3.*",
"canvas-size": "^2.0.0",
"dom-to-svg": "^0.12.2",
"handlebars": "^4.7.8",
"perfect-debounce": "^1.0.0",
Expand Down
16 changes: 16 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,13 @@ __metadata:
languageName: node
linkType: hard

"@types/canvas-size@npm:^1.2.2":
version: 1.2.2
resolution: "@types/canvas-size@npm:1.2.2"
checksum: d0e43a312677f7bd85530cc7d884119b0d55a1aaa60225c422e1df35f1bf88f0448c91d851161a53a74aa456e36d9cf1932e85cfe7dc6ee51a8e4ee33473aeb5
languageName: node
linkType: hard

"@types/eslint@npm:^9.6.0":
version: 9.6.1
resolution: "@types/eslint@npm:9.6.1"
Expand Down Expand Up @@ -4219,6 +4226,13 @@ __metadata:
languageName: node
linkType: hard

"canvas-size@npm:^2.0.0":
version: 2.0.0
resolution: "canvas-size@npm:2.0.0"
checksum: 04d1e5cdf99cda1471b9b4c8387cd06db0196e88f18ed25142dc9edbea487024ab52776c3dbe1dd087f0d4c7b685bb81bbb18a98b2c8441eff179c42db123539
languageName: node
linkType: hard

"chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
Expand Down Expand Up @@ -8823,6 +8837,7 @@ __metadata:
"@nuxt/image": latest
"@pinia/nuxt": ^0.5.4
"@popperjs/core": ^2.11.8
"@types/canvas-size": ^1.2.2
"@types/node": ^20.16.3
"@types/ramda": ^0.30.2
"@typescript-eslint/eslint-plugin": ^8.4.0
Expand All @@ -8833,6 +8848,7 @@ __metadata:
"@vueuse/core": ^11.0.3
"@vueuse/nuxt": ^11.0.3
bootstrap: 5.3.*
canvas-size: ^2.0.0
dom-to-svg: ^0.12.2
eslint: ^8.57.0
eslint-config-prettier: ^8.10.0
Expand Down

0 comments on commit 564333b

Please sign in to comment.