在 Android 上自訂簡報圖形

概述

本文說明如何透過編輯點與幾何路徑來編輯形狀幾何,以自訂 Aspose.Slides 簡報中的圖形。它示範如何使用 GeometryPath 和 IGeometryPath 變更既有圖形、執行基本路徑編輯操作、加入或移除點,並將更新後的幾何套用回圖形。

同時也示範如何建立自訂與複合圖形、建構具有圓角的圖形、判斷圖形幾何是否為閉合,以及使用 Android 點陣列檢查圖形元素。

使用編輯點變更圖形

在 PowerPoint 中,您可以使用 編輯點 來修改方形:

  • 將角落向內或向外移動。
  • 調整某點的曲率。
  • 新增點。

透過編輯點可以修改圖形的輪廓,或以既有圖形作為新圖形的基礎。

圖形編輯要點

overview_image

編輯 PowerPoint 圖形時,請留意以下概念:

  • 圖形(或其路徑)可以是閉合或開放的。
  • 圖形的路徑由透過線段相連的錨點組成。
  • 線段可以是直線或曲線。錨點與控制手柄決定其形狀。
  • 錨點可分為角點、直線點或平滑點:
    • 在角點,相鄰的線段可以形成角度。
    • 在平滑點,兩個控制手柄共線且與錨點的距離相等。
    • 在直線點,兩個控制手柄共線,但與錨點的距離可能不同。
  • 移動錨點或調整其手柄會改變圖形的輪廓。

為了透過編輯點編輯 PowerPoint 圖形,Aspose.Slides 提供了 GeometryPath 類別與 IGeometryPath 介面。

簡單編輯操作

使用下列方法編輯幾何路徑。接受索引的重載會在路徑的線段陣列中於零基索引位置插入線段。

操作 方法
追加或插入直線。 GeometryPath.lineTo
追加或插入具有兩個控制點與終點的三次貝茲曲線。 GeometryPath.cubicBezierTo
追加或插入具有一個控制點與終點的二次貝茲曲線。 GeometryPath.quadraticBezierTo
追加具有指定尺寸與角度(度)的橢圓弧。 GeometryPath.arcTo
透過將終點連回起點關閉目前圖形。 GeometryPath.closeFigure
設定圖形的起始點。 GeometryPath.moveTo
移除指定零基索引處的線段。 GeometryPath.removeAt

為圖形新增自訂點

此範例使用 GeometryPath.lineTo 在矩形圖形中插入兩個點,並使用 IGeometryShape.setGeometryPath 套用編輯後的路徑。座標為相對於圖形的本地座標:兩個新點均位於圖形中心,距左側 100 點、距上方 50 點。線段索引 1 與 4 分別將它們放在上緣與下緣,將這兩條邊向內拉。

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);

    IGeometryPath geometryPath = shape.getGeometryPaths()[0];
    geometryPath.lineTo(100, 50, 1);
    geometryPath.lineTo(100, 50, 4);

    shape.setGeometryPath(geometryPath);
} finally {
    presentation.dispose();
}

example1_image

從圖形移除點

此範例透過 GeometryPath.removeAt 移除零基索引 2 處的線段,變更心形輪廓。使用 IGeometryShape.setGeometryPath 套用修改後的路徑,即可更新圖形的外框。

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Heart, 100, 100, 300, 300);

    IGeometryPath path = shape.getGeometryPaths()[0];
    path.removeAt(2);

    shape.setGeometryPath(path);
} finally {
    presentation.dispose();
}

example2_image

建立自訂圖形

此範例使用 GeometryPath 建立一個五角星。交替的外徑與內徑分別為 100 點與 50 點,形成星尖與凹陷;每 72 度的步進使五個星尖均勻分布。路徑最後以 GeometryPath.closeFigure 閉合,並透過 IGeometryShape.setGeometryPath 套用到矩形圖形,取代其原始輪廓。

import com.aspose.slides.*;
import android.graphics.PointF;
import java.util.ArrayList;
import java.util.List;

List<PointF> points = new ArrayList<>();

float outerRadius = 100f;
float innerRadius = 50f;
int angleStep = 72;

for (int angle = -90; angle < 270; angle += angleStep)
{
    double radians = angle * (Math.PI / 180f);
    double x = outerRadius * Math.cos(radians);
    double y = outerRadius * Math.sin(radians);
    PointF point = new PointF((float)x + outerRadius, (float)y + outerRadius);
    points.add(point);

    radians = Math.PI * (angle + angleStep / 2) / 180.0;
    x = innerRadius * Math.cos(radians);
    y = innerRadius * Math.sin(radians);
    point = new PointF((float)x + outerRadius, (float)y + outerRadius);
    points.add(point);
}

GeometryPath starPath = new GeometryPath();
starPath.moveTo(points.get(0));

for (int i = 1; i < points.size(); i++)
{
    starPath.lineTo(points.get(i));
}

starPath.closeFigure();

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, outerRadius * 2, outerRadius * 2);
    shape.setGeometryPath(starPath);
} finally {
    presentation.dispose();
}

example3_image

建立複合自訂圖形

此範例為圖形的上三分之一與下三分之一分別定義兩個閉合的 GeometryPath 物件。使用 IGeometryShape.setGeometryPaths 同時套用兩條路徑,便可產生一個在中間三分之一留有間隙的單一複合圖形。

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);

    GeometryPath topPath = new GeometryPath();
    topPath.moveTo(0, 0);
    topPath.lineTo(shape.getWidth(), 0);
    topPath.lineTo(shape.getWidth(), shape.getHeight() / 3);
    topPath.lineTo(0, shape.getHeight() / 3);
    topPath.closeFigure();

    GeometryPath bottomPath = new GeometryPath();
    bottomPath.moveTo(0, shape.getHeight() / 3 * 2);
    bottomPath.lineTo(shape.getWidth(), shape.getHeight() / 3 * 2);
    bottomPath.lineTo(shape.getWidth(), shape.getHeight());
    bottomPath.lineTo(0, shape.getHeight());
    bottomPath.closeFigure();

    shape.setGeometryPaths(new GeometryPath[] { topPath, bottomPath });
} finally {
    presentation.dispose();
}

example4_image

建立具有圓角的自訂圖形

此範例將直線段與 GeometryPath.arcTo 結合,打造具有內彎圓角的圖形。每個角的大小設定弧的寬度與高度(以點為單位),且 -90 度的掃描角度繪製逆時針四分之一橢圓。完成的路徑以 IGeometryShape.setGeometryPath 套用,最後將簡報存為 PPTX 格式。

import com.aspose.slides.*;
import android.graphics.PointF;

float shapeX = 20f;
float shapeY = 20f;
float shapeWidth = 300f;
float shapeHeight = 200f;

float leftTopSize = 50f;
float rightTopSize = 20f;
float rightBottomSize = 40f;
float leftBottomSize = 10f;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Custom, shapeX, shapeY, shapeWidth, shapeHeight);

    PointF topLeft = new PointF(leftTopSize, 0);
    PointF topRight = new PointF(shapeWidth - rightTopSize, 0);
    PointF rightBottom = new PointF(shapeWidth, shapeHeight - rightBottomSize);
    PointF bottomLeft = new PointF(leftBottomSize, shapeHeight);
    PointF leftTop = new PointF(0, leftTopSize);

    GeometryPath geometryPath = new GeometryPath();
    geometryPath.moveTo(topLeft);
    geometryPath.lineTo(topRight);
    geometryPath.arcTo(rightTopSize, rightTopSize, 180, -90);
    geometryPath.lineTo(rightBottom);
    geometryPath.arcTo(rightBottomSize, rightBottomSize, -90, -90);
    geometryPath.lineTo(bottomLeft);
    geometryPath.arcTo(leftBottomSize, leftBottomSize, 0, -90);
    geometryPath.lineTo(leftTop);
    geometryPath.arcTo(leftTopSize, leftTopSize, 90, -90);
    geometryPath.closeFigure();

    shape.setGeometryPath(geometryPath);

    presentation.save("output.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

判斷圖形幾何是否為閉合

以下輔助程式會檢查每個非空幾何路徑是否以閉合線段結尾。它會讀取 IGeometryPath.getPathData,並將最終線段的 IPathSegment.getPathCommand 與 PathCommandType.Close 進行比較。若無非空路徑,則回傳 false。此檢查僅比對每條路徑的最後指令,不會檢查路徑內的每個子圖形。範例會對矩形輸出 true。

import com.aspose.slides.*;

class GeometryInspector {
    boolean isGeometryClosed(IGeometryShape geometryShape) {
        boolean hasNonemptyPath = false;

        for (IGeometryPath geometryPath : geometryShape.getGeometryPaths()) {
            IPathSegment[] segments = geometryPath.getPathData();
            if (segments.length == 0) {
                continue;
            }

            hasNonemptyPath = true;
            IPathSegment lastSegment = segments[segments.length - 1];
            if (lastSegment.getPathCommand() != PathCommandType.Close) {
                return false;
            }
        }

        return hasNonemptyPath;
    }
}

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);

    GeometryInspector inspector = new GeometryInspector();
    boolean closed = inspector.isGeometryClosed(shape);
    System.out.println(closed);
} finally {
    presentation.dispose();
}

擷取與檢查圖形元素

使用 IGeometryShape.createShapeElements 可將現有圖形檢視為包含路徑點與點類型的元素。將每個回傳的 IShapeElement 轉型為 ShapeElement,即可透過 getPathPoints 與 getPathTypes 方法取得點陣列與類型陣列。此表示方式適合輪廓分析或將幾何傳遞給自訂繪圖工具。

範例會開啟 input.pptx,在第一張投影片上直接尋找第一個幾何圖形,並列印其路徑線段。請使用包含直線與曲線線段的圖形測試,以觀察兩種輸出。此程式不會搜尋群組內部。

import com.aspose.slides.*;
import android.graphics.PointF;

Presentation presentation = new Presentation("input.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IGeometryShape shape = null;
    for (IShape candidate : slide.getShapes()) {
        if (candidate instanceof IGeometryShape) {
            shape = (IGeometryShape) candidate;
            break;
        }
    }

    if (shape == null) {
        System.out.println("No geometry shape found on the first slide.");
    } else {
        for (IShapeElement element : shape.createShapeElements()) {
            ShapeElement shapeElement = (ShapeElement) element;
            PointF[] points = shapeElement.getPathPoints();
            byte[] types = shapeElement.getPathTypes();
            System.out.println("Shape element:");

            for (int i = 0; i < points.length; i++) {
                int pointType = types[i] & 0x07; // 保留線段類型,排除旗標。
                switch (pointType) {
                    case 0:
                        System.out.println("Start figure at " + points[i]);
                        break;
                    case 1:
                        System.out.println("Line to " + points[i]);
                        break;
                    case 3:
                        System.out.println("Cubic Bezier: controls " + points[i] + ", " + points[i + 1] + "; end " + points[i + 2]);
                        i += 2; // Consume both control points and the endpoint.
                        break;
                }

                if ((types[i] & 0x80) != 0) {
                    System.out.println("Close subpath back to its starting point.");
                }
            }
        }
    }
} finally {
    presentation.dispose();
}

陣列依索引對應:points[i] 為座標點,types[i] 描述其角色。類型 0 代表開始圖形,類型 1 表示從前一點畫直線至當前點。三次貝茲線段以先前的終點為起點,接著使用三個連續的類型 3 條目:兩個控制點與新的終點。迴圈會一起處理這三條目,並檢查終點是否形成閉合。

點類型亦包含旗標。使用 0x07 掩碼可辨識線段類型,另以 0x80 判斷是否為閉合子路徑。例如 129 (0x81) 代表閉合的直線終點,131 (0x83) 代表閉合的貝茲終點。Java 以有號位元組儲存這些旗標;透過掩碼可保留正確的位元值。僅以 1 或 3 直接比較原始值會遺漏這些情況。其他旗標如虛線 (0x10) 與標記 (0x20) 不會改變基礎線段類型。

座標與變換。 點的單位為點 (1/72 吋),X 向右增加,Y 向下增加。對於未群組的圖形,擷取的座標已包含由 getX 與 getY 回傳的位置,且反映當前寬度與高度。例如,位於 (100, 200)、尺寸為 120 × 80 的未旋轉矩形,其輪廓界限為 (100, 200)–(220, 280)。請勿再次加上圖形的位置。將其 X、Y 扣除即可取得相對於未旋轉框架的座標。

擷取的點不會套用圖形的旋轉(由 getRotation 回傳)或水平、垂直翻轉。若要還原顯示出的輪廓,需在圖形中心周圍套用這些變換。對於群組圖形,還需考慮群組座標映射與變換;切勿假設擷取的點已是最終投影片座標,亦不要在已反映於子圖形框架的情況下再次平移或縮放。

檢查 vs. 編輯。 擷取的陣列僅描述輪廓,變更它們不會更新原始圖形。當您僅需點/類型陣列以進行分析或繪圖時,請使用此方式。若需在圖形本地座標系統中取得幾何指令或編輯幾何,請使用 getGeometryPaths。編輯後的路徑可透過 setGeometryPath 或 setGeometryPaths 套用。

常見問題

取代幾何後,填色與輪廓會發生什麼變化?

樣式仍屬於圖形本身,只有輪廓會改變。填色與輪廓會自動套用到新的幾何上。

如何正確地旋轉自訂圖形以及其幾何?

使用圖形的 setRotation 方法;幾何會隨圖形一起旋轉,因為它繫結於圖形的座標系統。

我可以把自訂圖形轉換成圖像以「鎖定」結果嗎?

可以。將所需的 slide 區域或 shape 本身匯出為點陣圖格式;這樣可簡化後續對複雜幾何的處理。