|
|
|
|
@ -13,6 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
@@ -13,6 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
|
|
|
|
|
import { isValidExcalidrawData, isValidLibrary } from "./json"; |
|
|
|
|
import { restore, restoreLibraryItems } from "./restore"; |
|
|
|
|
import { ImportedLibraryData } from "./types"; |
|
|
|
|
import { PNG } from "pngjs/browser"; |
|
|
|
|
|
|
|
|
|
const parseFileContents = async (blob: Blob | File) => { |
|
|
|
|
let contents: string; |
|
|
|
|
@ -233,75 +234,71 @@ const _canvasToBlob = async (canvas: HTMLCanvasElement): Promise<Blob> => {
@@ -233,75 +234,71 @@ const _canvasToBlob = async (canvas: HTMLCanvasElement): Promise<Blob> => {
|
|
|
|
|
export const canvasToBlob = async ( |
|
|
|
|
canvas: HTMLCanvasElement, |
|
|
|
|
): Promise<Blob> => { |
|
|
|
|
const chunkSize = 8000; // Adjust the chunk size according to your requirements
|
|
|
|
|
const tileWidth = 1000; |
|
|
|
|
const tileHeight = 1000; |
|
|
|
|
const tileDataArray: Uint8ClampedArray[][] = []; // Two-dimensional array to store tile data
|
|
|
|
|
|
|
|
|
|
const chunkBlobs: Blob[] = []; // Array to hold the generated image chunk Blobs
|
|
|
|
|
const { width: canvasWidth, height: canvasHeight } = canvas; |
|
|
|
|
|
|
|
|
|
console.log("canvas", canvas.width, canvas.height); |
|
|
|
|
const ctx = canvas.getContext("2d"); |
|
|
|
|
|
|
|
|
|
// Split the canvas into chunks and generate the image chunks
|
|
|
|
|
for (let x = 0; x < canvas.width; x += chunkSize) { |
|
|
|
|
for (let y = 0; y < canvas.height; y += chunkSize) { |
|
|
|
|
const chunkCanvas = document.createElement("canvas"); |
|
|
|
|
chunkCanvas.width = chunkSize; |
|
|
|
|
chunkCanvas.height = chunkSize; |
|
|
|
|
const chunkContext = chunkCanvas.getContext("2d"); |
|
|
|
|
|
|
|
|
|
if (!chunkContext) { |
|
|
|
|
throw new Error("Could not get context"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Copy the portion of the main canvas into the chunk canvas
|
|
|
|
|
chunkContext.drawImage( |
|
|
|
|
canvas, |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
chunkSize, |
|
|
|
|
chunkSize, |
|
|
|
|
0, |
|
|
|
|
0, |
|
|
|
|
chunkSize, |
|
|
|
|
chunkSize, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
console.log(x, y, chunkSize); |
|
|
|
|
if (!ctx) { |
|
|
|
|
throw new Error("No canvas context"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Convert the chunk canvas to a Blob
|
|
|
|
|
const blob = await _canvasToBlob(chunkCanvas); |
|
|
|
|
chunkBlobs.push(blob); |
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
chunkCanvas.remove(); |
|
|
|
|
// 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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Convert each Blob into an ArrayBuffer and concatenate them
|
|
|
|
|
const arrayBuffers = await Promise.all( |
|
|
|
|
chunkBlobs.map((blob) => { |
|
|
|
|
return new Promise<ArrayBuffer>((resolve) => { |
|
|
|
|
const reader = new FileReader(); |
|
|
|
|
reader.onloadend = () => { |
|
|
|
|
if (reader.result instanceof ArrayBuffer) { |
|
|
|
|
resolve(reader.result); |
|
|
|
|
} else { |
|
|
|
|
throw new Error("Failed to read ArrayBuffer"); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
reader.readAsArrayBuffer(blob); |
|
|
|
|
}); |
|
|
|
|
}), |
|
|
|
|
); |
|
|
|
|
const totalLength = arrayBuffers.reduce( |
|
|
|
|
(length, buffer) => length + buffer.byteLength, |
|
|
|
|
0, |
|
|
|
|
); |
|
|
|
|
const concatenatedBuffer = new Uint8Array(totalLength); |
|
|
|
|
let offset = 0; |
|
|
|
|
for (const buffer of arrayBuffers) { |
|
|
|
|
concatenatedBuffer.set(new Uint8Array(buffer), offset); |
|
|
|
|
offset += buffer.byteLength; |
|
|
|
|
// Create a new PNG image with the final dimensions
|
|
|
|
|
const finalImage = new PNG({ width: canvasWidth, height: canvasHeight }); |
|
|
|
|
|
|
|
|
|
// 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; |
|
|
|
|
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]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new Blob([concatenatedBuffer], { type: "image/png" }); |
|
|
|
|
const buffer = PNG.sync.write(finalImage); |
|
|
|
|
|
|
|
|
|
return new Blob([buffer], { type: "image/png" }); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** generates SHA-1 digest from supplied file (if not supported, falls back |
|
|
|
|
|