浏览代码

Update rotation/flip architecture, simplify image widget.

Jason Grout 7 年之前
父节点
当前提交
c53acb6098

+ 16 - 18
packages/imageviewer-extension/src/index.ts

@@ -33,10 +33,10 @@ namespace CommandIDs {
   const flipVertical = 'imageviewer:flip-vertical';
 
   export
-  const rotateClockwise = 'imageviewer:rotate-clockwise';
+  const rotateRight = 'imageviewer:rotate-right';
 
   export
-  const rotateCounterclockwise = 'imageviewer:rotate-counterclockwise';
+  const rotateLeft = 'imageviewer:rotate-left';
 
   export
   const invertColors = 'imageviewer:invert-colors';
@@ -113,7 +113,7 @@ function activate(app: JupyterLab, palette: ICommandPalette, restorer: ILayoutRe
 
   const category = 'Image Viewer';
 
-  [CommandIDs.zoomIn, CommandIDs.zoomOut, CommandIDs.resetImage, CommandIDs.rotateClockwise, CommandIDs.rotateCounterclockwise, CommandIDs.flipHorizontal, CommandIDs.flipVertical, CommandIDs.invertColors]
+  [CommandIDs.zoomIn, CommandIDs.zoomOut, CommandIDs.resetImage, CommandIDs.rotateRight, CommandIDs.rotateLeft, CommandIDs.flipHorizontal, CommandIDs.flipVertical, CommandIDs.invertColors]
     .forEach(command => { palette.addItem({ command, category }); });
 
   return tracker;
@@ -153,15 +153,15 @@ function addCommands(app: JupyterLab, tracker: IImageTracker) {
     isEnabled
   });
 
-  commands.addCommand('imageviewer:rotate-clockwise', {
-    execute: rotateClockwise,
-    label: 'Rotate Clockwise',
+  commands.addCommand('imageviewer:rotate-right', {
+    execute: rotateRight,
+    label: 'Rotate Right (CW)',
     isEnabled
   });
 
-  commands.addCommand('imageviewer:rotate-counterclockwise', {
-    execute: rotateCounterclockwise,
-    label: 'Rotate Counterclockwise',
+  commands.addCommand('imageviewer:rotate-left', {
+    execute: rotateLeft,
+    label: 'Rotate Left (CCW)',
     isEnabled
   });
 
@@ -204,26 +204,24 @@ function addCommands(app: JupyterLab, tracker: IImageTracker) {
 
     if (widget) {
       widget.scale = 1;
-      widget.rotation = 0;
-      widget.horizontalflip = 1;
-      widget.verticalflip = 1;
       widget.colorinversion = 0;
+      widget.resetRotationFlip();
     }
   }
 
-  function rotateClockwise(): void {
+  function rotateRight(): void {
     const widget = tracker.currentWidget;
 
     if (widget) {
-      widget.rotation += 90;
+      widget.rotateRight();
     }
   }
 
-  function rotateCounterclockwise(): void {
+  function rotateLeft(): void {
     const widget = tracker.currentWidget;
 
     if (widget) {
-      widget.rotation -= 90;
+      widget.rotateLeft();
     }
   }
 
@@ -231,7 +229,7 @@ function addCommands(app: JupyterLab, tracker: IImageTracker) {
     const widget = tracker.currentWidget;
 
     if (widget) {
-      widget.horizontalflip *= -1;
+      widget.flipHorizontal();
     }
   }
 
@@ -239,7 +237,7 @@ function addCommands(app: JupyterLab, tracker: IImageTracker) {
     const widget = tracker.currentWidget;
 
     if (widget) {
-      widget.verticalflip *= -1;
+      widget.flipVertical();
     }
   }
 

+ 84 - 56
packages/imageviewer/src/widget.ts

@@ -37,11 +37,19 @@ class ImageViewer extends Widget implements DocumentRegistry.IReadyWidget {
    * Construct a new image widget.
    */
   constructor(context: DocumentRegistry.Context) {
-    super({ node: Private.createNode() });
+    super();
     this.context = context;
     this.node.tabIndex = -1;
     this.addClass(IMAGE_CLASS);
 
+    this._orientation = document.createElement('div');
+    this._orientation.textContent = '↸';
+    this._orientation.classList.add('jp-ImageViewer-orientation')
+    this.node.appendChild(this._orientation);
+
+    this._img = document.createElement('img');
+    this.node.appendChild(this._img);
+
     this._onTitleChanged();
     context.pathChanged.connect(this._onTitleChanged, this);
 
@@ -83,58 +91,56 @@ class ImageViewer extends Widget implements DocumentRegistry.IReadyWidget {
   }
 
   /**
-   * The rotation of the image.
+   * The color inversion of the image.
    */
-  get rotation(): number {
-    return this._rotation;
+  get colorinversion(): number {
+    return this._colorinversion;
   }
-  set rotation(value: number) {
-    if (value === this._rotation) {
+  set colorinversion(value: number) {
+    if (value === this._colorinversion) {
         return;
     }
-    this._rotation = value % 360;
+    this._colorinversion = value;
     this.updateStyle();
   }
 
   /**
-   * The horizontal flip of the image.
+   * Reset rotation and flip transformations.
    */
-  get horizontalflip(): number {
-    return this._horizontalflip;
-  }
-  set horizontalflip(value: number) {
-    if (value === this._horizontalflip) {
-        return;
-    }
-    this._horizontalflip = value;
+  resetRotationFlip(): void {
+    this._matrix = [1, 0, 0, 1];
     this.updateStyle();
   }
 
   /**
-   * The vertical flip of the image.
+   * Rotate the image left (counter-clockwise).
    */
-  get verticalflip(): number {
-    return this._verticalflip;
+  rotateLeft(): void {
+    this._matrix = Private.prod(this._matrix, Private.rotateLeftMatrix);
+    this.updateStyle();
   }
-  set verticalflip(value: number) {
-    if (value === this._verticalflip) {
-        return;
-    }
-    this._verticalflip = value;
+
+  /**
+   * Rotate the image right (clockwise).
+   */
+  rotateRight(): void {
+    this._matrix = Private.prod(this._matrix, Private.rotateRightMatrix);
     this.updateStyle();
   }
 
   /**
-   * The color inversion of the image.
+   * Flip the image horizontally.
    */
-  get colorinversion(): number {
-    return this._colorinversion;
+  flipHorizontal(): void {
+    this._matrix = Private.prod(this._matrix, Private.flipHMatrix);
+    this.updateStyle();
   }
-  set colorinversion(value: number) {
-    if (value === this._colorinversion) {
-        return;
-    }
-    this._colorinversion = value;
+
+  /**
+   * Flip the image vertically.
+   */
+  flipVertical(): void {
+    this._matrix = Private.prod(this._matrix, Private.flipVMatrix);
     this.updateStyle();
   }
 
@@ -172,31 +178,25 @@ class ImageViewer extends Widget implements DocumentRegistry.IReadyWidget {
       return;
     }
     let content = context.model.toString();
-    let src = `data:${cm.mimetype};${cm.format},${content}`;
-    let node = this.node.querySelector('img') as HTMLImageElement;
-    node.setAttribute('src', src);
+    this._img.src = `data:${cm.mimetype};${cm.format},${content}`;
   }
 
   private updateStyle(): void {
-      let transformString: string;
-      let filterString: string;
-
-      transformString = `translate(-50%,-50%) `;
-      transformString += `scale(${this._scale * this._horizontalflip},${this._scale * this._verticalflip}) `;
-      transformString += `rotate(${this._rotation}deg)`;
-      filterString = `invert(${this._colorinversion})`;
+    let [a, b, c, d] = this._matrix;
+    let [tX, tY] = Private.prodVec(this._matrix, [1, 1]);
+    let transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0) translate(${tX < 0 ? -100 : 0}%, ${tY < 0 ? -100 : 0}%) `;
+    this._img.style.transform = `scale(${this._scale}) ${transform}`;
+    this._orientation.style.transform = transform;
 
-      let rotNode = this.node.querySelector('div') as HTMLElement;
-      rotNode.style.transform = transformString;
-      rotNode.style.filter = filterString;
+    this._img.style.filter = `invert(${this._colorinversion})`;
   }
 
   private _scale = 1;
-  private _rotation = 0;
-  private _horizontalflip = 1;
-  private _verticalflip = 1;
+  private _matrix = [1, 0, 0, 1];
   private _colorinversion = 0;
   private _ready = new PromiseDelegate<void>();
+  private _img: HTMLImageElement;
+  private _orientation: HTMLDivElement;
 }
 
 
@@ -218,15 +218,43 @@ class ImageViewerFactory extends ABCWidgetFactory<ImageViewer, DocumentRegistry.
  */
 namespace Private {
   /**
-   * Create the node for the image widget.
+   * Multiply 2x2 matrices.
    */
   export
-  function createNode(): HTMLElement {
-    let node = document.createElement('div');
-    let innerNode = document.createElement('div');
-    let image = document.createElement('img');
-    node.appendChild(innerNode);
-    innerNode.appendChild(image);
-    return node;
+  function prod([a11, a12, a21, a22]: number[], [b11, b12, b21, b22]: number[]): number[] {
+    return [a11 * b11 + a12 * b21, a11 * b12 + a12 * b22,
+            a21 * b11 + a22 * b21, a21 * b12 + a22 * b22];
   }
+
+  /**
+   * Multiply a 2x2 matrix and a 2x1 vector.
+   */
+  export
+  function prodVec([a11, a12, a21, a22]: number[], [b1, b2]: number[]): number[] {
+    return [a11 * b1 + a12 * b2, a21 * b1 + a22 * b2];
+  }
+
+  /**
+   * Clockwise rotation transformation matrix.
+   */
+  export
+  const rotateRightMatrix = [0, 1, -1, 0];
+
+  /**
+   * Counter-clockwise rotation transformation matrix.
+   */
+  export
+  const rotateLeftMatrix = [0, -1, 1, 0];
+
+  /**
+   * Horizontal flip transformation matrix.
+   */
+  export
+  const flipHMatrix = [-1, 0, 0, 1];
+
+  /**
+   * Vertical flip transformation matrix.
+   */
+  export
+  const flipVMatrix = [1, 0, 0, -1];
 }

+ 15 - 12
packages/imageviewer/style/index.css

@@ -8,22 +8,25 @@
   overflow: auto;
 }
 
-
-.jp-ImageViewer > div {
-  position: absolute;
-  transform-origin: center center;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  z-index: -1;
+.jp-ImageViewer > img {
+  box-sizing: border-box;
+  transform-origin: top left;
+  max-width:100%;
+  max-height:100%;
 }
 
-
-.jp-ImageViewer img {
-  margin: auto;
+.jp-ImageViewer-orientation {
+  box-sizing: border-box;
+  position: absolute;
+  transform-origin: top left;
+  top: 10px;
+  left: 10px;
+  font-size: 30px;
+  font-weight: 900;
+  z-index: 1;
+  background-color: rgba(255, 255, 255, 0.8);
 }
 
-
 .jp-ImageViewer::before {
     content: '';
     display: block;

+ 4 - 4
packages/shortcuts-extension/schema/plugin.json

@@ -175,19 +175,19 @@
       },
       "type": "object"
     },
-    "imageviewer:rotate-clockwise": {
+    "imageviewer:rotate-right": {
       "default": { },
       "properties": {
-        "command": { "default": "imageviewer:rotate-clockwise" },
+        "command": { "default": "imageviewer:rotate-right" },
         "keys": { "default": ["]"] },
         "selector": { "default": ".jp-ImageViewer" }
       },
       "type": "object"
     },
-    "imageviewer:rotate-counterclockwise": {
+    "imageviewer:rotate-left": {
       "default": { },
       "properties": {
-        "command": { "default": "imageviewer:rotate-counterclockwise" },
+        "command": { "default": "imageviewer:rotate-left" },
         "keys": { "default": ["["] },
         "selector": { "default": ".jp-ImageViewer" }
       },