Calvert's murmur

使用 RVG 繪圖

2017-03-15

約 7266 字 / 需 40 分鐘閱讀

原文:RMagick User’s Guide and ReferenceDrawing with RVG

介紹

RVG(Ruby 向量圖形)是 RMagick 的 Draw 類別的外觀,它提供了基於 W3C 推薦的可縮放向量圖形(Scalable Vector Graphics)的繪圖 API。

RVG 是一個可縮放向量繪圖函式庫。可縮放意味著圖形不是固定的像素尺寸。相同的圖形可以被呈現在螢幕上或列印出來。向量圖使用了幾何物件,如直線和圓。與點陣圖不同,向量圖在放大時不會「像素化」。

作為 RVG 函式庫的介紹,讓我們來看看如何畫出上面這隻小鴨。以下是完整的程式。

require 'rvg/rvg'
include Magick

RVG::dpi = 72

rvg = RVG.new(2.5.in, 2.5.in).viewbox(0,0,250,250) do |canvas|
  canvas.background_fill = 'white'

  canvas.g.translate(100, 150).rotate(-30) do |body|
    body.styles(:fill=>'yellow', :stroke=>'black', :stroke_width=>2)
    body.ellipse(50, 30)
    body.rect(45, 20, -20, -10).skewX(-35)
  end

  canvas.g.translate(130, 83) do |head|
    head.styles(:stroke=>'black', :stroke_width=>2)
    head.circle(30).styles(:fill=>'yellow')
    head.circle(5, 10, -5).styles(:fill=>'black')
    head.polygon(30,0, 70,5, 30,10, 62,25, 23,20).styles(:fill=>'orange')
  end

  foot = RVG::Group.new do |_foot|
    _foot.path('m0,0 v30 l30,10 l5,-10, l-5,-10 l-30,10z').
    styles(:stroke_width=>2, :fill=>'orange', :stroke=>'black')
  end
  canvas.use(foot).translate(75, 188).rotate(15)
  canvas.use(foot).translate(100, 185).rotate(-15)

  canvas.text(125, 30) do |title|
    title.tspan("duck|").styles(:text_anchor=>'end', :font_size=>20,
      :font_family=>'helvetica', :fill=>'black')
    title.tspan("type").styles(:font_size=>22,
      :font_family=>'times', :font_style=>'italic', :fill=>'red')
    end
    canvas.rect(249,249).styles(:stroke=>'blue', :fill=>'none')
  end

rvg.draw.write('duck.gif')

摘要

所有的繪圖都遵循相同的 3 個步驟:

  1. 建立一個 RVG 物件。指定最終圖片的寬度和高度。轉交程式碼區塊給 RVG.new 方法。
  2. 在程式區塊內,在 RVG 物件調用方法來指定背景,加入形狀、文字或點陣圖,或加入一組形狀、文字或點陣圖。
  3. 調用 draw 方法在背景繪出形狀、文字或點陣圖。

我將逐行介紹範例。

第 1 ~ 3 行

require 'rvg/rvg'
include Magick

這只是平常用來載入 RVG 擴展的 Ruby 程式碼。為了少打一些字,我引入了 Magick 模組到物件的命名空間。

第 4 ~ 6 行

RVG::dpi = 72

rvg = RVG.new(2.5.in, 2.5.in).viewbox(0,0,250,250) do |canvas|

RVG::dpi 啟用了在 RVG 使用單位方法。當你將 RVG::dpi 設定為非 nil 值時,RVG 向整數和浮點數類別加入了許多轉換方法。這些方法允許你使用如英吋、毫米和厘米來指定測量單位。DPI 代表「每英吋點數」圖片解析度。這裡我設定 RVG::dpi 為 72,一個螢幕常用的數值。

RVG.new 需要 2 個參數。這些參數以像素指定最終圖片的寬度和高度。由於我定義了 RVG::dpi,我可以透過 in 轉換方法使用英吋指定這些數值。在 72dpi,最終圖片的邊長將會是 2.5 * 72 = 180 像素。

預設情況下,RVG 使用像素作為其測量單位,但由於我正在繪製一個可縮放的圖片,我不想限制自己的像素。viewbox 方法定義了一個具有邏輯單位的座標系。viewbox 需要 4 個參數,min_xmin_ywidthheight。在第 6 行,我定義了我的座標系,它的原點在 (0,0),寬度和高度為 250 單位。透過使用我自己的座標系,我可以稍後將圖片的尺寸更改為 5 英吋正方形或 1 英吋正方形,只需要更改 new 的參數。

預設座標系

預設情況下,RVG 座標系的原點在左上角。X 軸向右為正值,Y 軸向下為正值。上面的圖片顯示了該座標系的座標軸。我為範例圖片加上了淺藍色方格紙背景,以協助將坐標參數與圖片中的實際位置相關聯。只要記住,座標軸和方格紙背景實際上不是我產生的圖片的一部分。

RVG 類別是 RVG 定義的容器類別之一。容器物件可以包含圖形物件,如圓和線、文字、點陣圖及其他容器物件。最外面的容器永遠是 RVG 物件。我將加入所有組成鴨子的圖形物件到此容器。

我們通常會轉交一個程式區塊給容器建構方法。但是,在這裡我連接了 viewboxnew,所以 viewbox 負責產生並傳遞新的 RVG 實例到 canvas 參數。

第 7 行

canvas.background_fill = 'white'

預設情況下,RVG 圖形是繪製在透明背景上。當你想要顯示你的圖片在另一個圖片上時,這是很方便的。你可以透過為 background_fill= 屬性指定顏色來覆蓋預設背景顏色。這裡我設定背景顏色為「白色」。

第 9 ~ 13 行

canvas.g.translate(100, 150).rotate(-30) do |body|
  body.styles(:fill=>'yellow', :stroke=>'black', :stroke_width=>2)
  body.ellipse(50, 30)
  body.rect(45, 20, -20, -10).skewX(-35)
end

這幾行做了很多事情,調用 7 個方法,所以讓我們一次介紹一個方法。

Groups(群組)

Group 是 RVG 中的第二個容器類別。群組的目的是將一組座標系變形和一組樣式與群組中的圖形物件相關聯。要在另一個容器中建立群組物件,可調用容器上的 g 方法。如果程式區塊存在的話,g 方法會轉交給程式區塊。在這個範例中,沒有與 g 關聯的程式區塊,所以 g 返回了新群組。g 方法加入群組到其容器的內容。在這個範例中,群組的容器是在第 6 行建立的 canvas 物件。群組中的圖形物件會作為繪製容器的一部分被繪製。連接到 gtranslaterotate,透過_新增坐標系變形_來修改群組。

(那邊有一個程式區塊,但有個 2 方法調用在 g 和程式區塊間。我稍後會解釋。)

Transforms(變形)

我將使用這個群組來包含組成鴨子身體的橢圓和組成翅膀的矩形。我可以只指定 x 和 y 座標來定位這些形狀相對於原點的位置,但移動原點到我想繪製形狀的位置更容易。這是 translate 方法的目的。這個方法將原點移動到由其參數指定的(x,y)位置。我在群組物件上調用 translate,並且由群組的內容取得為此群組指定的座標系變形,橢圓和矩形將在相對於舊座標系原點的座標(100,150)上繪製。

另外,我想讓鴨子的身體向上傾斜,所以我使用 rotate 方法來旋轉座標軸。rotate 的參數是旋轉的角度。負數表示逆時針旋轉。

在平移及旋轉座標系後,座標軸看起來像這樣:

變形方法

有六種變形方法,除了 translaterotate 外,還有 scaleskewXskewYmatrix。當巢狀群組時,在內部群組上定義的任何變形都會被增加入外部變形。

Styles(樣式)

回想一下,styles 方法修改了預設的群組樣式。styles 方法需要一個雜湊作為參數。雜湊的鍵是樣式名稱,雜湊的值為樣式值。在這個範例中,有三個樣式名稱。:fill 樣式將填充顏色設定為「黃色」。:stroke 樣式將邊框顏色設定為「黑色」。:stroke_width 樣式將邊框寬度設定為「2」。我想要樣式套用在全組內的所有物件,所以第 10 行我在新的群組物件上調用 style

styles 方法是真正的主力。它幾乎在每個 RVG 的類別內都有定義,除了這三個之外還有許多其他樣式名稱。

Basic shapes(基本形狀)

該群組包含了兩個基本形狀,一個橢圓和一個矩形。我使用 ellipse 方法將橢圓加入群組。ellipse 有四個參數,前兩個是在 x 軸及 y 軸上的半徑,是必要的。後兩個是中心點的 (x,y) 座標,當如同此處省略時,會預設為 (0,0)。我使用 rect 方法將矩形加入,它也有四個參數。前兩個是矩形的寬與高,後兩個是左上角的 (x,y) 座標。這兩個方法都會回傳自己本身,所以你可以連接其他方法給他們。

這是群組呈現時的樣子。橢圓以原點為中心,矩形的左上角稍微向上並且在原點的左邊。

形狀方法

有七種形狀方法,除了 ellipserect 外,還有 circlelinepathpolygonpolyline。你也可以把文字當成形狀。形狀可以加上邊框和填充顏色,也可以透過變形方法和 styles 方法進行修改。

SkewX(X 方向傾斜)

每個人都知道翅膀看起來不像矩形!翅膀看起來像傾斜的平行四邊形。(嗯,在這個範例是這樣!)幸運的是,我們可以如同對容器一樣在形狀使用變形。skewX 方法讓我們很容易傾斜矩形。skewX 方法是另一個變形,它需要一個參數,傾斜 x 軸的角度。由於所有的形狀建構式包含 rect,都會回傳自己本身,我可以將 skewX 直接連接到 rect,並限制只有矩形有變形效果。結果如下所示。(我為翅膀座標系畫了座標軸。)

身體就是這樣。在繼續之前讓我們來完成其他瑣事。我之前說過容器建構式(如 g)會轉交給程式區塊如果存在的話。在這個範例,translaterotate 方法介於 g 和程式區塊間。如果有相關聯的程式區塊,所有變形方法都會轉交過去,所以我可以很容易地將他們連接到容器建構式,並仍然使用程式區塊參數來定義群組內的圖形物件。方法連結是 RVG 常見的慣用方法,你會在範例中看到很多。

下一個群組繪製頭部。

第 15 ~ 20 行

canvas.g.translate(130, 83) do |head|
  head.styles(:stroke=>'black', :stroke_width=>2)
  head.circle(30).styles(:fill=>'yellow')
  head.circle(5, 10, -5).styles(:fill=>'black')
  head.polygon(30,0, 70,5, 30,10, 62,25, 23,20).styles(:fill=>'orange')
end

這個部分與前一部分非常相似。我要定義一個群組來包含繪製鴨子頭部、眼睛和喙的圖形物件。首先,我使用平移方法來將原點移動到 (130,83):

在第 16 行我在群組定義了 strokstroke_width。在群組上定義的樣式會傳播到群組中的形狀,除非你覆蓋他們。要做到這一點,在形狀調用 styles。在這個群組,每個形狀都有自己的填充顏色。黃色的圓組成了頭部,circle 需要 3 個參數,第一個參數是圓的半徑,另外兩個參數是中心點的 (x,y) 座標。當如同此處省略時,會預設為 (0,0)。我使用一個小黑圓作為眼睛。

最後,我使用 polygon 方法來繪製鳥喙。此方法使用一系列的 (x,y) 座標繪製多邊形。如果最後一個座標不等於第一個,polygon 會暗中的加上它以關閉多邊形。同樣地,我使用 styles 將填充顏色設定為「橘色」。

第 22 ~ 25 行

foot = RVG::Group.new do |_foot|
  _foot.path('m0,0 v30 l30,10 l5,-10, l-5,-10 l-30,10z').
    styles(:stroke_width=>2, :fill=>'orange', :stroke=>'black')
end

這裡我透過直接調用 new 來建立一個群組,而不是在容器上調用 g 方法。這將建立一個不包含在畫布中的群組物件。你可以想像腳沒有附加到任何東西,像這樣:

第 26 ~ 27 行

canvas.use(foot).translate(75, 188).rotate(15)
canvas.use(foot).translate(100, 185).rotate(-15)

要將群組加入到畫布,我使用 use 方法。use 方法可以接受任何容器或圖形物件作為參數。你還可以指定一個 (x,y) 座標來放置物件。然而,在這個範例中,我讓那些參數預設為 (0,0),並使用 translate 來決定腳的位置。以下展示了左腳如何附加到鴨子上:

當然,鴨子在走路,所以我必須用 rotate 讓腳有點傾斜:

附加右腳很簡單,再次調用 use 但使用不同的參數來 translaterotate

第 29 ~ 34 行

canvas.text(125, 30) do |title|
  title.tspan("duck|").styles(:text_anchor=>'end', :font_size=>20,
    :font_family=>'helvetica', :fill=>'black')
  title.tspan("type").styles(:font_size=>22,
    :font_family=>'times', :font_style=>'italic', :fill=>'red')
end

我現在需要的是一個圖片標題。文字在 RVG 是 text 方法的工作。和形狀方法相同,text 可以與任何容器物件一起使用。text 本身是一個容器,除了它只能包含文字相關的物件。text 方法接受 2 或 3 個參數,一個 (x,y) 座標對和一個可選的字串。(x,y) 座標對定義目前文字開始呈現的位置,如果有一個字串參數,它將從目前文字位置開始被呈現。呈現文字會改變目前文字位置到文字的結尾。

在這個範例中,文字被作為一個容器,文字物件可以包含 tspan 物件。每個 tspan 可以指定它自己的樣式。預設情況下,每個 tspan 會從目前文字位置開始呈現。

同樣地,我可以使用 styles 改變文字外觀。我在這裡選擇字體、字體樣式(預設是「normal」)、以點計算的大小和顏色。

第 35 行

canvas.rect(249,249).styles(:stroke=>'blue', :fill=>'none')

就快完成了,我現在需要的是加上一個藍色邊框。(我現在要刪除方格紙背景,因為我們不再需要它。)

第 38 行

rvg.draw.write('duck.gif')

調用 draw 方法沒有佔用大量空間,只有 4 個字母,但是做了很多工作。draw 方法會遍歷我加到最外層 RVG 容器的所有圖形物件,並在背景繪製他們。繪製完成後,draw 將以 RMagick Image 物件的形式回傳圖像。你可以在圖像上使用任何 Image 類別方法,這裡我簡單地將圖像輸出為 GIF 檔案。

縮放圖片

RVG 圖片真的可以縮放嗎?讓我們試試看。更改 RVG.new 調用讓圖片變成 4 倍大。邊長是 5 英吋:

rvg = RVG.new(5.in, 5.in).viewbox(0,0,250,250) do |canvas|

不改變其他東西。再次執行程式,看看你得到什麼。