在 Python 中自訂簡報形狀

概述

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

同時也展示如何建立自訂與合成圖形、建構帶有圓角的圖形、判斷圖形幾何是否為閉合,以及提取圖形元素以進行輪廓分析。

使用編輯點變更圖形

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

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

編輯點讓您能修改圖形的輪廓,或以現有圖形為基礎建立新圖形。

圖形編輯技巧

overview_image

編輯 PowerPoint 圖形時,請記住下列概念:

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

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

簡單的編輯操作

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

操作 方法
附加或插入直線。 GeometryPath.line_to
附加或插入具有兩個控制點與終點的三次貝茲曲線。 GeometryPath.cubic_bezier_to
附加或插入具有一個控制點與終點的二次貝茲曲線。 GeometryPath.quadratic_bezier_to
附加具有指定尺寸與角度(以度為單位)的橢圓弧。 GeometryPath.arc_to
透過將終點連回起點來關閉目前圖形。 GeometryPath.close_figure
設定圖形的起始點。 GeometryPath.move_to
移除位於零基索引的線段。 GeometryPath.remove_at

為圖形新增自訂點

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

import aspose.slides as slides

with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 200, 100)

    geometry_path = shape.get_geometry_paths()[0]
    geometry_path.line_to(100, 50, 1)
    geometry_path.line_to(100, 50, 4)

    shape.set_geometry_path(geometry_path)

example1_image

從圖形移除點

此範例透過使用 GeometryPath.remove_at 移除零基索引 2 處的線段,來更改心形輪廓。再以 GeometryShape.set_geometry_path 套用修改後的路徑,即可更新圖形的輪廓。

import aspose.slides as slides

with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    shape = slide.shapes.add_auto_shape(slides.ShapeType.HEART, 100, 100, 300, 300)

    path = shape.get_geometry_paths()[0]
    path.remove_at(2)

    shape.set_geometry_path(path)

example2_image

建立自訂圖形

此範例使用 GeometryPath 建立一個五角星。外徑與內徑交替分別為 100 點與 50 點,以定義星尖與凹陷;每 72 度的步進將五個星尖均勻分布。路徑最後以 GeometryPath.close_figure 關閉,並透過 GeometryShape.set_geometry_path 套用至矩形圖形,取代其原有輪廓。

import aspose.slides as slides
import aspose.pydrawing as draw
import math

points = []

outer_radius = 100
inner_radius = 50
angle_step = 72

for angle in range(-90, 270, angle_step):
    radians = angle * (math.pi / 180)
    x = outer_radius * math.cos(radians)
    y = outer_radius * math.sin(radians)
    points.append(slides.PointF(x + outer_radius, y + outer_radius))

    radians = math.pi * (angle + angle_step / 2) / 180.0
    x = inner_radius * math.cos(radians)
    y = inner_radius * math.sin(radians)
    points.append(slides.PointF(x + outer_radius, y + outer_radius))

star_path = slides.GeometryPath()
star_path.move_to(points[0])

for i in range(1, len(points)):
    star_path.line_to(points[i])

star_path.close_figure()

with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, outer_radius * 2, outer_radius * 2)
    shape.set_geometry_path(star_path)

example3_image

建立合成自訂圖形

此範例為圖形的上三分之一與下三分之一各定義一個閉合的 GeometryPath。再以 GeometryShape.set_geometry_paths 同時套用兩條路徑,即可產生一個在中間三分之一留有空隙的單一合成圖形。

import aspose.slides as slides

with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 200, 100)

    top_path = slides.GeometryPath()
    top_path.move_to(0, 0)
    top_path.line_to(shape.width, 0)
    top_path.line_to(shape.width, shape.height / 3)
    top_path.line_to(0, shape.height / 3)
    top_path.close_figure()

    bottom_path = slides.GeometryPath()
    bottom_path.move_to(0, shape.height / 3 * 2)
    bottom_path.line_to(shape.width, shape.height / 3 * 2)
    bottom_path.line_to(shape.width, shape.height)
    bottom_path.line_to(0, shape.height)
    bottom_path.close_figure()

    shape.set_geometry_paths([top_path, bottom_path])

example4_image

建立帶有圓角的自訂圖形

此範例結合直線段與 GeometryPath.arc_to 以建立具有內彎圓角的圖形。每個角的尺寸以點為單位設定弧的寬度與高度,-90 度的掃描角則繪製逆時針四分之一橢圓。完成的路徑使用 GeometryShape.set_geometry_path 套用,然後將簡報另存為 PPTX 格式。

import aspose.slides as slides
import aspose.pydrawing as draw

shape_x = 20
shape_y = 20
shape_width = 300
shape_height = 200

left_top_size = 50
right_top_size = 20
right_bottom_size = 40
left_bottom_size = 10

with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    shape = slide.shapes.add_auto_shape(slides.ShapeType.CUSTOM, shape_x, shape_y, shape_width, shape_height)

    top_left = slides.PointF(left_top_size, 0)
    top_right = slides.PointF(shape_width - right_top_size, 0)
    right_bottom = slides.PointF(shape_width, shape_height - right_bottom_size)
    bottom_left = slides.PointF(left_bottom_size, shape_height)
    left_top = slides.PointF(0, left_top_size)

    geometry_path = slides.GeometryPath()
    geometry_path.move_to(top_left)
    geometry_path.line_to(top_right)
    geometry_path.arc_to(right_top_size, right_top_size, 180, -90)
    geometry_path.line_to(right_bottom)
    geometry_path.arc_to(right_bottom_size, right_bottom_size, -90, -90)
    geometry_path.line_to(bottom_left)
    geometry_path.arc_to(left_bottom_size, left_bottom_size, 0, -90)
    geometry_path.line_to(left_top)
    geometry_path.arc_to(left_top_size, left_top_size, 90, -90)
    geometry_path.close_figure()

    shape.set_geometry_path(geometry_path)

    presentation.save("output.pptx", slides.export.SaveFormat.PPTX)

判斷圖形幾何是否為閉合

以下輔助程式會檢查每個非空幾何路徑是否以閉合線段結尾。它會讀取 GeometryPath.path_data 並將最後一個線段的 PathSegment.path_command 與 PathCommandType.CLOSE 進行比較。若不存在非空路徑則返回 false。此檢查僅針對每條路徑的最終指令,並不會檢查路徑內部的每個子圖形。範例會對矩形輸出 True。

import aspose.slides as slides


def is_geometry_closed(geometry_shape):
    has_nonempty_path = False

    for geometry_path in geometry_shape.get_geometry_paths():
        segments = geometry_path.path_data
        if len(segments) == 0:
            continue

        has_nonempty_path = True
        last_segment = segments[len(segments) - 1]
        if last_segment.path_command != slides.PathCommandType.CLOSE:
            return False

    return has_nonempty_path


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 200, 100)
    print(is_geometry_closed(shape))

提取並檢查圖形元素

使用 GeometryShape.create_shape_elements 可將現有圖形檢視為包含路徑點與點類型的元素集合。每個回傳的 ShapeElement 皆會公開其 path_points 與 path_types 陣列。此表示方式對於輪廓分析或將幾何傳遞給自訂繪圖工具相當有用。

範例會開啟簡報、直接在第一張投影片上找到第一個幾何圖形,並列印其路徑線段。請使用包含直線與曲線段的圖形來觀察兩種輸出。此方法不會搜尋群組內部。

import aspose.slides as slides

with slides.Presentation("input.pptx") as presentation:
    slide = presentation.slides[0]
    shape = next((item for item in slide.shapes if isinstance(item, slides.GeometryShape)), None)

    if shape is None:
        print("No geometry shape found on the first slide.")
    else:
        for element in shape.create_shape_elements():
            points = element.path_points
            types = element.path_types
            print("Shape element:")

            i = 0
            while i < len(points):
                point_type = types[i] & 0x07  # Keep the segment type, excluding flags.
                if point_type == 0:
                    print(f"Start figure at {points[i]}")
                elif point_type == 1:
                    print(f"Line to {points[i]}")
                elif point_type == 3:
                    print(f"Cubic Bezier: controls {points[i]}, {points[i + 1]}; end {points[i + 2]}")
                    i += 2  # Consume both control points and the endpoint.

                if (types[i] & 0x80) != 0:
                    print("Close subpath back to its starting point.")
                i += 1

陣列以索引對應:points[i] 為一個點,types[i] 描述其角色。類型 0 表示開始一個圖形,類型 1 表示從前一點畫線到當前點。三次貝茲線段從前一終點開始,使用連續三個類型為 3 的項目:兩個控制點加上新的終點。迴圈會一次消費這三個項目,並檢查終點是否閉合。

點類型也包含旗標。以 0x07 掩碼可辨識線段類型,另以 0x80 判斷是否為閉合子路徑。例如 129(0x81)代表一個會閉合子路徑的線段終點,131(0x83)代表會閉合的貝茲終點。僅以 1 或 3 直接比較原始值會遺漏這些情況。其他旗標,如虛線 (0x10) 與標記 (0x20),不會改變基礎線段類型。

座標與變換。 點的單位為點 (1/72 英吋),X 向右增加、Y 向下增加。對於未群組的圖形,提取的座標已包含其 x 與 y 位置,且反映其當前寬度與高度。例如,未旋轉的矩形位於 (100, 200)、尺寸為 120 × 80,其輪廓界限為 (100, 200)–(220, 280)。請勿再次加上圖形的位置。若要取得相對於未旋轉框架的座標,請減去其 X 與 Y。

提取的點不會套用圖形的 rotation 或水平、垂直翻轉。若要復原顯示的輪廓,請在圖形中心周圍套用這些變換。對於群組圖形,亦需考慮群組座標映射與變換;切勿假設提取的點即為最終渲染在投影片上的座標,或在已於子圖形框架中反映的情況下再次套用平移或縮放。

檢查 vs. 編輯。 提取的陣列僅描述輪廓,修改它們不會更新原始圖形。當您的分析或繪圖程式需要點/類型陣列時,請使用此方式。若需要在圖形本地座標系統中取得幾何指令或想要編輯其幾何,請使用 get_geometry_paths。編輯完成後,請透過 set_geometry_path 或 set_geometry_paths 套用。

常見問題

取代幾何後,填色與輪廓會怎樣?

樣式仍屬於圖形本身,僅輪廓會改變。填色與輪廓會自動套用至新幾何。

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

使用圖形的 rotation 屬性;幾何會隨圖形一起旋轉,因為它綁定於圖形的座標系統。

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

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