Selaa lähdekoodia

Stop using getTransform (#950)

* Stop using getTransform

Fixes #861

The original motivation behind this is to make it work with Firefox. But it also helped make the code more intentional.

Test Plan:
- Create one square, select it, zoom in repeatedly, make sure that it zooms centered in the screen and everything looks good
- Scroll at various zoom levels, things look good
- Export a small scene at 1x and 3x, make sure the background is properly set and look good

* fix selection element
Christopher Chedeau 5 vuotta sitten
vanhempi
commit
b20d4539c0

+ 1 - 0
src/components/App.tsx

@@ -2246,6 +2246,7 @@ export class App extends React.Component<any, AppState> {
       elements,
       this.state,
       this.state.selectionElement,
+      window.devicePixelRatio,
       this.rc!,
       this.canvas!,
       {

+ 4 - 0
src/renderer/renderElement.ts

@@ -303,6 +303,10 @@ export function renderElement(
       context.fillStyle = "rgba(0, 0, 255, 0.10)";
       context.fillRect(0, 0, element.width, element.height);
       context.fillStyle = fillStyle;
+      context.translate(
+        -element.x - sceneState.scrollX,
+        -element.y - sceneState.scrollY,
+      );
       break;
     }
     case "rectangle":

+ 22 - 62
src/renderer/renderScene.ts

@@ -12,7 +12,6 @@ import {
   SCROLLBAR_COLOR,
   SCROLLBAR_WIDTH,
 } from "../scene/scrollbars";
-import { getZoomTranslation } from "../scene/zoom";
 import { getSelectedElements } from "../scene/selection";
 
 import { renderElement, renderElementToSvg } from "./renderElement";
@@ -28,6 +27,7 @@ export function renderScene(
   elements: readonly ExcalidrawElement[],
   appState: AppState,
   selectionElement: ExcalidrawElement | null,
+  scale: number,
   rc: RoughCanvas,
   canvas: HTMLCanvasElement,
   sceneState: SceneState,
@@ -51,46 +51,11 @@ export function renderScene(
 
   const context = canvas.getContext("2d")!;
 
-  // Get initial scale transform as reference for later usage
-  const initialContextTransform = context.getTransform();
-
   // When doing calculations based on canvas width we should used normalized one
-  const normalizedCanvasWidth =
-    canvas.width / getContextTransformScaleX(initialContextTransform);
-  const normalizedCanvasHeight =
-    canvas.height / getContextTransformScaleY(initialContextTransform);
-
-  const zoomTranslation = getZoomTranslation(canvas, sceneState.zoom);
-  function applyZoom(context: CanvasRenderingContext2D): void {
-    context.save();
-
-    // Handle zoom scaling
-    context.setTransform(
-      getContextTransformScaleX(initialContextTransform) * sceneState.zoom,
-      0,
-      0,
-      getContextTransformScaleY(initialContextTransform) * sceneState.zoom,
-      getContextTransformTranslateX(context.getTransform()),
-      getContextTransformTranslateY(context.getTransform()),
-    );
-    // Handle zoom translation
-    context.setTransform(
-      getContextTransformScaleX(context.getTransform()),
-      0,
-      0,
-      getContextTransformScaleY(context.getTransform()),
-      getContextTransformTranslateX(initialContextTransform) -
-        zoomTranslation.x,
-      getContextTransformTranslateY(initialContextTransform) -
-        zoomTranslation.y,
-    );
-  }
-  function resetZoom(context: CanvasRenderingContext2D): void {
-    context.restore();
-  }
+  const normalizedCanvasWidth = canvas.width / scale;
+  const normalizedCanvasHeight = canvas.height / scale;
 
   // Paint background
-  context.save();
   if (typeof sceneState.viewBackgroundColor === "string") {
     const hasTransparence =
       sceneState.viewBackgroundColor === "transparent" ||
@@ -99,12 +64,20 @@ export function renderScene(
     if (hasTransparence) {
       context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
     }
+    const fillStyle = context.fillStyle;
     context.fillStyle = sceneState.viewBackgroundColor;
     context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
+    context.fillStyle = fillStyle;
   } else {
     context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
   }
-  context.restore();
+
+  // Apply zoom
+  const zoomTranslationX = (-normalizedCanvasWidth * (sceneState.zoom - 1)) / 2;
+  const zoomTranslationY =
+    (-normalizedCanvasHeight * (sceneState.zoom - 1)) / 2;
+  context.translate(zoomTranslationX, zoomTranslationY);
+  context.scale(sceneState.zoom, sceneState.zoom);
 
   // Paint visible elements
   const visibleElements = elements.filter(element =>
@@ -116,15 +89,12 @@ export function renderScene(
     ),
   );
 
-  applyZoom(context);
   visibleElements.forEach(element => {
     renderElement(element, rc, context, renderOptimizations, sceneState);
   });
-  resetZoom(context);
 
   // Pain selection element
   if (selectionElement) {
-    applyZoom(context);
     renderElement(
       selectionElement,
       rc,
@@ -132,7 +102,6 @@ export function renderScene(
       renderOptimizations,
       sceneState,
     );
-    resetZoom(context);
   }
 
   // Pain selected elements
@@ -140,7 +109,6 @@ export function renderScene(
     const selectedElements = getSelectedElements(elements, appState);
     const dashledLinePadding = 4 / sceneState.zoom;
 
-    applyZoom(context);
     context.translate(sceneState.scrollX, sceneState.scrollY);
     selectedElements.forEach(element => {
       const [
@@ -166,11 +134,10 @@ export function renderScene(
       context.lineWidth = lineWidth;
       context.setLineDash(initialLineDash);
     });
-    resetZoom(context);
+    context.translate(-sceneState.scrollX, -sceneState.scrollY);
 
     // Paint resize handlers
     if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
-      applyZoom(context);
       context.translate(sceneState.scrollX, sceneState.scrollY);
       context.fillStyle = "#fff";
       const handlers = handlerRectangles(selectedElements[0], sceneState.zoom);
@@ -183,10 +150,14 @@ export function renderScene(
           context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
           context.lineWidth = lineWidth;
         });
-      resetZoom(context);
+      context.translate(-sceneState.scrollX, -sceneState.scrollY);
     }
   }
 
+  // Reset zoom
+  context.scale(1 / sceneState.zoom, 1 / sceneState.zoom);
+  context.translate(-zoomTranslationX, -zoomTranslationY);
+
   // Paint remote pointers
   for (const clientId in sceneState.remotePointerViewportCoords) {
     let { x, y } = sceneState.remotePointerViewportCoords[clientId];
@@ -237,7 +208,8 @@ export function renderScene(
       sceneState,
     );
 
-    context.save();
+    const fillStyle = context.fillStyle;
+    const strokeStyle = context.strokeStyle;
     context.fillStyle = SCROLLBAR_COLOR;
     context.strokeStyle = "rgba(255,255,255,0.8)";
     [scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => {
@@ -252,7 +224,8 @@ export function renderScene(
         );
       }
     });
-    context.restore();
+    context.fillStyle = fillStyle;
+    context.strokeStyle = strokeStyle;
     return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
   }
 
@@ -317,16 +290,3 @@ export function renderSceneToSvg(
     );
   });
 }
-
-function getContextTransformScaleX(transform: DOMMatrix): number {
-  return transform.a;
-}
-function getContextTransformScaleY(transform: DOMMatrix): number {
-  return transform.d;
-}
-function getContextTransformTranslateX(transform: DOMMatrix): number {
-  return transform.e;
-}
-function getContextTransformTranslateY(transform: DOMMatrix): number {
-  return transform.f;
-}

+ 1 - 0
src/scene/export.ts

@@ -42,6 +42,7 @@ export function exportToCanvas(
     elements,
     appState,
     null,
+    scale,
     rough.canvas(tempCanvas),
     tempCanvas,
     {

+ 1 - 1
src/scene/index.ts

@@ -17,4 +17,4 @@ export {
   hasText,
 } from "./comparisons";
 export { createScene } from "./createScene";
-export { getZoomOrigin, getZoomTranslation, getNormalizedZoom } from "./zoom";
+export { getZoomOrigin, getNormalizedZoom } from "./zoom";

+ 0 - 13
src/scene/zoom.ts

@@ -16,19 +16,6 @@ export function getZoomOrigin(canvas: HTMLCanvasElement | null) {
   };
 }
 
-export function getZoomTranslation(canvas: HTMLCanvasElement, zoom: number) {
-  const diffMiddleOfTheCanvas = {
-    x: (canvas.width / 2) * (zoom - 1),
-    y: (canvas.height / 2) * (zoom - 1),
-  };
-
-  // Due to JavaScript float precision, we fix to fix decimals count to have symmetric zoom
-  return {
-    x: parseFloat(diffMiddleOfTheCanvas.x.toFixed(8)),
-    y: parseFloat(diffMiddleOfTheCanvas.y.toFixed(8)),
-  };
-}
-
 export function getNormalizedZoom(zoom: number): number {
   const normalizedZoom = parseFloat(zoom.toFixed(2));
   const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2));