Bläddra i källkod

Merge pull request #5612 from BabylonJS/ninepatch

Ninepatch
David Catuhe 6 år sedan
förälder
incheckning
0fedb17829

BIN
Playground/textures/panel_blue2x.9.direct.png


BIN
Playground/textures/panel_blue2x.9.inv.png


BIN
Playground/textures/panel_blue2x.9.png


+ 1 - 0
dist/preview release/what's new.md

@@ -21,6 +21,7 @@
   - Added support for clipboard events to let users perform `cut`, `copy` and `paste` events ([Saket Saurabh](https://github.com/ssaket))
   - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/) / [Deltakosh](https://github.com/deltakosh))
   - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
+  - Added support for [nine patch stretch](https://www.babylonjs-playground.com/#G5H9IN#2) mode for images. ([Deltakosh](https://github.com/deltakosh))
 
 ## Updates
 

+ 206 - 0
gui/src/2D/controls/image.ts

@@ -6,6 +6,8 @@ import { Measure } from "2D";
  * Class used to create 2D images
  */
 export class Image extends Control {
+    private static _WorkingCanvas: Nullable<HTMLCanvasElement> = null;
+
     private _domImage: HTMLImageElement;
     private _imageWidth: number;
     private _imageHeight: number;
@@ -23,6 +25,12 @@ export class Image extends Control {
     private _cellHeight: number = 0;
     private _cellId: number = -1;
 
+    private _populateNinePatchSlicesFromImage = false;
+    private _sliceLeft: number;
+    private _sliceRight: number;
+    private _sliceTop: number;
+    private _sliceBottom: number;
+
     /**
      * Observable notified when the content is loaded
      */
@@ -36,6 +44,93 @@ export class Image extends Control {
     }
 
     /**
+     * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data
+     */
+    public get populateNinePatchSlicesFromImage(): boolean {
+        return this._populateNinePatchSlicesFromImage;
+    }
+
+    public set populateNinePatchSlicesFromImage(value: boolean) {
+        if (this._populateNinePatchSlicesFromImage === value) {
+            return;
+        }
+
+        this._populateNinePatchSlicesFromImage = value;
+
+        if (this._populateNinePatchSlicesFromImage && this._loaded) {
+            this._extractNinePatchSliceDataFromImage();
+        }
+    }
+
+    /**
+     * Gets or sets the left value for slicing (9-patch)
+     */
+    public get sliceLeft(): number {
+        return this._sliceLeft;
+    }
+
+    public set sliceLeft(value: number) {
+        if (this._sliceLeft === value) {
+            return;
+        }
+
+        this._sliceLeft = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the right value for slicing (9-patch)
+     */
+    public get sliceRight(): number {
+        return this._sliceRight;
+    }
+
+    public set sliceRight(value: number) {
+        if (this._sliceRight === value) {
+            return;
+        }
+
+        this._sliceRight = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the top value for slicing (9-patch)
+     */
+    public get sliceTop(): number {
+        return this._sliceTop;
+    }
+
+    public set sliceTop(value: number) {
+        if (this._sliceTop === value) {
+            return;
+        }
+
+        this._sliceTop = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the bottom value for slicing (9-patch)
+     */
+    public get sliceBottom(): number {
+        return this._sliceBottom;
+    }
+
+    public set sliceBottom(value: number) {
+        if (this._sliceBottom === value) {
+            return;
+        }
+
+        this._sliceBottom = value;
+
+        this._markAsDirty();
+    }
+
+    /**
      * Gets or sets the left coordinate in the source image
      */
     public get sourceLeft(): number {
@@ -163,6 +258,10 @@ export class Image extends Control {
         this._imageHeight = this._domImage.height;
         this._loaded = true;
 
+        if (this._populateNinePatchSlicesFromImage) {
+            this._extractNinePatchSliceDataFromImage();
+        }
+
         if (this._autoScale) {
             this.synchronizeSizeWithContent();
         }
@@ -172,6 +271,56 @@ export class Image extends Control {
         this._markAsDirty();
     }
 
+    private _extractNinePatchSliceDataFromImage() {
+        if (!Image._WorkingCanvas) {
+            Image._WorkingCanvas = document.createElement('canvas');
+        }
+        const canvas = Image._WorkingCanvas;
+        const context = canvas.getContext('2d')!;
+        const width = this._domImage.width;
+        const height = this._domImage.height;
+
+        canvas.width = width;
+        canvas.height = height;
+
+        context.drawImage(this._domImage, 0, 0, width, height);
+        const imageData = context.getImageData(0, 0, width, height);
+
+        // Left and right
+        this._sliceLeft = -1;
+        this._sliceRight = -1;
+        for (var x = 0; x < width; x++) {
+            const alpha = imageData.data[x * 4 + 3];
+
+            if (alpha > 127 && this._sliceLeft === -1) {
+                this._sliceLeft = x;
+                continue;
+            }
+
+            if (alpha < 127 && this._sliceLeft > -1) {
+                this._sliceRight = x;
+                break;
+            }
+        }
+
+        // top and bottom
+        this._sliceTop = -1;
+        this._sliceBottom = -1;
+        for (var y = 0; y < height; y++) {
+            const alpha = imageData.data[y * width * 4 + 3];
+
+            if (alpha > 127 && this._sliceTop === -1) {
+                this._sliceTop = y;
+                continue;
+            }
+
+            if (alpha < 127 && this._sliceTop > -1) {
+                this._sliceBottom = y;
+                break;
+            }
+        }
+    }
+
     /**
      * Gets or sets image source url
      */
@@ -345,12 +494,67 @@ export class Image extends Control {
                     context.drawImage(this._domImage, x, y, width, height,
                         this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                     break;
+                case Image.STRETCH_NINE_PATCH:
+                    this._renderNinePatch(context);
+                    break;
             }
         }
 
         context.restore();
     }
 
+    private _renderCornerPatch(context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, targetX: number, targetY: number): void {
+        context.drawImage(this._domImage, x, y, width, height, this._currentMeasure.left + targetX, this._currentMeasure.top + targetY, width, height);
+    }
+
+    private _renderNinePatch(context: CanvasRenderingContext2D): void {
+        let height = this._imageHeight;
+        let leftWidth = this._sliceLeft;
+        let topHeight = this._sliceTop;
+        let bottomHeight = this._imageHeight - this._sliceBottom;
+        let rightWidth = this._imageWidth - this._sliceRight;
+        let left = 0;
+        let top = 0;
+
+        if (this._populateNinePatchSlicesFromImage) {
+            left = 1;
+            top = 1;
+            height -= 2;
+            leftWidth -= 1;
+            topHeight -= 1;
+            bottomHeight -= 1;
+            rightWidth -= 1;
+        }
+
+        const centerWidth = this._sliceRight - this._sliceLeft + 1;
+        const targetCenterWidth = this._currentMeasure.width - rightWidth - this.sliceLeft + 1;
+        const targetTopHeight = this._currentMeasure.height - height + this._sliceBottom;
+
+        // Corners
+        this._renderCornerPatch(context, left, top, leftWidth, topHeight, 0, 0);
+        this._renderCornerPatch(context, left, this._sliceBottom, leftWidth, height - this._sliceBottom, 0, targetTopHeight);
+
+        this._renderCornerPatch(context, this._sliceRight, top, rightWidth, topHeight, this._currentMeasure.width - rightWidth, 0);
+        this._renderCornerPatch(context, this._sliceRight, this._sliceBottom, rightWidth, height - this._sliceBottom, this._currentMeasure.width - rightWidth, targetTopHeight);
+
+        // Center
+        context.drawImage(this._domImage, this._sliceLeft, this._sliceTop, centerWidth, this._sliceBottom - this._sliceTop + 1,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top + topHeight, targetCenterWidth, targetTopHeight - topHeight + 1);
+
+        // Borders
+        context.drawImage(this._domImage, left, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
+            this._currentMeasure.left, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
+
+        context.drawImage(this._domImage, this._sliceRight, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
+            this._currentMeasure.left + this._currentMeasure.width - rightWidth, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
+
+        context.drawImage(this._domImage, this._sliceLeft, top, centerWidth, topHeight,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top, targetCenterWidth, topHeight);
+
+        context.drawImage(this._domImage, this._sliceLeft, this._sliceBottom, centerWidth, bottomHeight,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top + targetTopHeight, targetCenterWidth, bottomHeight);
+    }
+
     public dispose() {
         super.dispose();
         this.onImageLoadedObservable.clear();
@@ -365,4 +569,6 @@ export class Image extends Control {
     public static readonly STRETCH_UNIFORM = 2;
     /** STRETCH_EXTEND */
     public static readonly STRETCH_EXTEND = 3;
+    /** NINE_PATCH */
+    public static readonly STRETCH_NINE_PATCH = 4;
 }

+ 2 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/gui/imagePropertyGridComponent.tsx

@@ -27,7 +27,8 @@ export class ImagePropertyGridComponent extends React.Component<IImagePropertyGr
             { label: "None", value: BABYLON.GUI.Image.STRETCH_NONE },
             { label: "Fill", value: BABYLON.GUI.Image.STRETCH_FILL },
             { label: "Uniform", value: BABYLON.GUI.Image.STRETCH_UNIFORM },
-            { label: "Extend", value: BABYLON.GUI.Image.STRETCH_EXTEND }
+            { label: "Extend", value: BABYLON.GUI.Image.STRETCH_EXTEND },
+            { label: "NinePatch", value: BABYLON.GUI.Image.STRETCH_NINE_PATCH }
         ];
 
         return (