Compare commits

...

3 Commits

Author SHA1 Message Date
Arnošt Pleskot
671ed94d74
chore: add timers 2023-06-16 23:42:45 +02:00
Arnošt Pleskot
d5ac76d4ea
feat: working export with pngjs 2023-06-16 22:36:24 +02:00
Arnošt Pleskot
2b19d53549
feat: init of generating blobs in chunk 2023-06-14 11:52:16 +02:00
5 changed files with 102 additions and 3 deletions

View File

@ -44,6 +44,7 @@
"png-chunk-text": "1.0.0", "png-chunk-text": "1.0.0",
"png-chunks-encode": "1.0.0", "png-chunks-encode": "1.0.0",
"png-chunks-extract": "1.0.0", "png-chunks-extract": "1.0.0",
"pngjs": "7.0.0",
"points-on-curve": "0.2.0", "points-on-curve": "0.2.0",
"pwacompat": "2.0.17", "pwacompat": "2.0.17",
"react": "18.2.0", "react": "18.2.0",
@ -74,6 +75,7 @@
"@types/lodash.throttle": "4.1.7", "@types/lodash.throttle": "4.1.7",
"@types/pako": "1.0.3", "@types/pako": "1.0.3",
"@types/pica": "5.1.3", "@types/pica": "5.1.3",
"@types/pngjs": "6.0.1",
"@types/react": "18.0.15", "@types/react": "18.0.15",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",
"@types/resize-observer-browser": "0.1.7", "@types/resize-observer-browser": "0.1.7",

View File

@ -13,6 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
import { isValidExcalidrawData, isValidLibrary } from "./json"; import { isValidExcalidrawData, isValidLibrary } from "./json";
import { restore, restoreLibraryItems } from "./restore"; import { restore, restoreLibraryItems } from "./restore";
import { ImportedLibraryData } from "./types"; import { ImportedLibraryData } from "./types";
import { PNG } from "pngjs/browser";
const parseFileContents = async (blob: Blob | File) => { const parseFileContents = async (blob: Blob | File) => {
let contents: string; let contents: string;
@ -210,9 +211,7 @@ export const loadLibraryFromBlob = async (
return parseLibraryJSON(await parseFileContents(blob), defaultStatus); return parseLibraryJSON(await parseFileContents(blob), defaultStatus);
}; };
export const canvasToBlob = async ( const _canvasToBlob = async (canvas: HTMLCanvasElement): Promise<Blob> => {
canvas: HTMLCanvasElement,
): Promise<Blob> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
canvas.toBlob((blob) => { canvas.toBlob((blob) => {
@ -232,6 +231,86 @@ export const canvasToBlob = async (
}); });
}; };
export const canvasToBlob = async (
canvas: HTMLCanvasElement,
): Promise<Blob> => {
const tileWidth = 1000;
const tileHeight = 1000;
const tileDataArray: Uint8ClampedArray[][] = []; // Two-dimensional array to store tile data
const { width: canvasWidth, height: canvasHeight } = canvas;
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("No canvas context");
}
// Function to process each tile
function processTile(tileX: number, tileY: number) {
// Calculate the starting and ending coordinates for the tile
const startX = tileX * tileWidth;
const startY = tileY * tileHeight;
const endX = Math.min(startX + tileWidth, canvasWidth);
const endY = Math.min(startY + tileHeight, canvasHeight);
// Get the image data for the tile directly from the main canvas
const imageData = ctx!.getImageData(
startX,
startY,
endX - startX,
endY - startY,
).data;
// Store the tile data in the two-dimensional array
tileDataArray[tileY] = tileDataArray[tileY] || [];
tileDataArray[tileY][tileX] = imageData;
}
console.time("tiling");
// Iterate over the tiles and process each one
for (let tileY = 0; tileY < canvasHeight / tileHeight; tileY++) {
for (let tileX = 0; tileX < canvasWidth / tileWidth; tileX++) {
processTile(tileX, tileY);
}
}
console.timeEnd("tiling");
console.time("create png");
// Create a new PNG image with the final dimensions
const finalImage = new PNG({ width: canvasWidth, height: canvasHeight });
console.timeEnd("create png");
console.time("concat tiles");
// Merge the tiles into the final image
for (let tileY = 0; tileY < canvasHeight / tileHeight; tileY++) {
for (let tileX = 0; tileX < canvasWidth / tileWidth; tileX++) {
const imageData = tileDataArray[tileY][tileX];
const destX = tileX * tileWidth;
const destY = tileY * tileHeight;
// Copy the pixels from the tile to the final image
for (let y = 0; y < tileHeight; y++) {
for (let x = 0; x < tileWidth; x++) {
const index = (y * tileWidth + x) * 4;
const destIndex = ((destY + y) * canvasWidth + destX + x) * 4;
finalImage.data[destIndex] = imageData[index];
finalImage.data[destIndex + 1] = imageData[index + 1];
finalImage.data[destIndex + 2] = imageData[index + 2];
finalImage.data[destIndex + 3] = imageData[index + 3];
}
}
}
}
console.timeEnd("concat tiles");
console.time("create buffer");
const buffer = PNG.sync.write(finalImage);
console.timeEnd("create buffer");
return new Blob([buffer], { type: "image/png" });
};
/** generates SHA-1 digest from supplied file (if not supported, falls back /** generates SHA-1 digest from supplied file (if not supported, falls back
to a 40-char base64 random id) */ to a 40-char base64 random id) */
export const generateIdFromFile = async (file: File): Promise<FileId> => { export const generateIdFromFile = async (file: File): Promise<FileId> => {

View File

@ -75,7 +75,9 @@ export const exportCanvas = async (
document.body.appendChild(tempCanvas); document.body.appendChild(tempCanvas);
if (type === "png") { if (type === "png") {
console.time("export png");
let blob = await canvasToBlob(tempCanvas); let blob = await canvasToBlob(tempCanvas);
console.timeEnd("export png");
tempCanvas.remove(); tempCanvas.remove();
if (appState.exportEmbedScene) { if (appState.exportEmbedScene) {
blob = await ( blob = await (

4
src/global.d.ts vendored
View File

@ -120,3 +120,7 @@ declare module "image-blob-reduce" {
const reduce: ImageBlobReduce.ImageBlobReduceStatic; const reduce: ImageBlobReduce.ImageBlobReduceStatic;
export = reduce; export = reduce;
} }
declare module "pngjs/browser" {
export { PNG } from "pngjs";
}

View File

@ -2714,6 +2714,13 @@
resolved "https://registry.yarnpkg.com/@types/pica/-/pica-5.1.3.tgz#5ef64529a1f83f7d6586a8bf75a8a00be32aca02" resolved "https://registry.yarnpkg.com/@types/pica/-/pica-5.1.3.tgz#5ef64529a1f83f7d6586a8bf75a8a00be32aca02"
integrity sha512-13SEyETRE5psd9bE0AmN+0M1tannde2fwHfLVaVIljkbL9V0OfFvKwCicyeDvVYLkmjQWEydbAlsDsmjrdyTOg== integrity sha512-13SEyETRE5psd9bE0AmN+0M1tannde2fwHfLVaVIljkbL9V0OfFvKwCicyeDvVYLkmjQWEydbAlsDsmjrdyTOg==
"@types/pngjs@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-6.0.1.tgz#c711ec3fbbf077fed274ecccaf85dd4673130072"
integrity sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg==
dependencies:
"@types/node" "*"
"@types/prettier@^2.1.5": "@types/prettier@^2.1.5":
version "2.7.2" version "2.7.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0"
@ -8227,6 +8234,11 @@ png-chunks-extract@1.0.0:
dependencies: dependencies:
crc-32 "^0.3.0" crc-32 "^0.3.0"
pngjs@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26"
integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==
points-on-curve@0.2.0, points-on-curve@^0.2.0: points-on-curve@0.2.0, points-on-curve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1" resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1"