Ver código fonte

feat: working export with pngjs

Arnošt Pleskot 2 anos atrás
pai
commit
d5ac76d4ea
4 arquivos alterados com 74 adições e 59 exclusões
  1. 2 0
      package.json
  2. 56 59
      src/data/blob.ts
  3. 4 0
      src/global.d.ts
  4. 12 0
      yarn.lock

+ 2 - 0
package.json

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

+ 56 - 59
src/data/blob.ts

@@ -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> => {
 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

+ 4 - 0
src/global.d.ts

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

+ 12 - 0
yarn.lock

@@ -2714,6 +2714,13 @@
   resolved "https://registry.yarnpkg.com/@types/pica/-/pica-5.1.3.tgz#5ef64529a1f83f7d6586a8bf75a8a00be32aca02"
   integrity sha512-13SEyETRE5psd9bE0AmN+0M1tannde2fwHfLVaVIljkbL9V0OfFvKwCicyeDvVYLkmjQWEydbAlsDsmjrdyTOg==
 
+"@types/[email protected]":
+  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":
   version "2.7.2"
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0"
@@ -8227,6 +8234,11 @@ [email protected]:
   dependencies:
     crc-32 "^0.3.0"
 
[email protected]:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26"
+  integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==
+
 [email protected], points-on-curve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1"