本文說明如何通過實現
BufferedImageOp
接口來編寫自定義 Java 2D 圖像處理類。它使用一個 2D
細胞自動機
(CA),即
循環空間
,來構造圖像處理應用程序。CA 會 “操作” 圖像(例如,一個 PEG 文件),使圖像不斷地按有趣的方式轉換。我希望本文能開闊您的視野,使您能編寫一個全新的圖像處理應用程序類。
2D 細胞自動機由分布在 2D 網格(通常稱為 布局 )中的 細胞 組成。每個細胞都有一個 狀態 ,可以是 0 到 n 之間的任意整數。清單 1 顯示了如何用 Java 代碼聲明一個細胞自動機布局:
清單 1. 定義
TwoDCellularAutomaton.universe
|
所有細胞每個時刻都同時更新狀態。一個細胞的新狀態取決于該細胞的當前狀態和它相鄰細胞的當前狀態,狀態的轉換根據特定的規則進行。清單 2 更新了下一時刻的布局:
清單 2.
TwoDCellularAutomaton
類(部分清單)
public void update() { |
不同類型的 CA 更新單個細胞所用的規則不相同。規則的定義由子類完成。
![]() |
|
在循環空間中,每個細胞都有一個狀態,它是 n 種狀態中的一種。每個細胞的初始狀態通常是隨機定義的,也就是說,是 0 和 n - 1 (包括 0 和 n - 1)之間的一個隨機數字。細胞的鄰居定義為 von Neumann 鄰居 :包括它的上下左右 4 個鄰近細胞。
清單 3 通過給出每個細胞鄰居和細胞本身的不同坐標來定義該細胞的 von Neumann 鄰居:
清單 3. 定義
TwoDCellularAutomaton.VON_NEUMANN_NEIGHBORHOOD
|
循環空間由以下規則定義:
如果一個細胞的狀態是 k ,它有一個鄰居的狀態是 k + 1 ,那么該狀態在下一時刻將會有一個新的狀態 k + 1 。否則,該細胞的狀態將保持不變。
這個規則是循環的,因此,如果一個細胞處于狀態 n - 1 ,而且有一個狀態為 0 的鄰居,那么該細胞在下一時刻的狀態將為 0 。
![]() |
|
這個簡單規則會導致意想不到的復雜行為。清單 4 實現了在循環空間中更新細胞的規則:
清單 4. 定義
CyclicSpace.updateCell(int, int)
|
我曾說過,循環空間布局的初始狀態是隨機的。細胞會被 “更大的” 細胞 “吃掉”,最后會再次循環回到狀態 0 。在這個過程中,區域自行組織并展開,成為波浪形。最后,會出現一個穩定的波浪圖案。這些波浪呈對角線在布局中移動,看上去有點像紙風車。
![]() ![]() |
java.awt.image.BufferedImageOp
接口允許您創建自己的圖像操作器(也稱為
過濾器
)。本文只討論
BufferedImageOp
的一個方法:
BufferedImage filter(BufferedImage src, BufferedImage dest) |
src
和
dest
是 2D 像素網格。實現此方法時,您可以按任意方式從
src
構建
dest
。普遍做法是在
src
中迭代像素,并按照一定規則在
dest
中創建相應的像素。這就是在圖像處理應用程序中需要做的事情,我根據著名的法國畫家 Georges-Pierre Seurat 將它命名為
Seurat
(參見
下載
獲取完整的示例代碼)。
您可能知道圖像像素與 CA 中的細胞存在映射關系。它們都以 2D 網格形式存在,每個都有狀態,對于像素就是它的紅綠藍(RGB)值。我將在
filter(BufferedImage src, BufferedImage dest)
實現中探討這種映射關系。對于
src
中的每個像素,我會根據一定規則將該像素的 RGB 值與 CA 中相應細胞的狀態組合起來,創建
dest
中相應像素的新 RGB 值。這個規則將定義一個過濾器。
清單 5 顯示如何迭代
src
中的所有像素并在
dest
中構建像素。抽象方法
getNewRGB(Color)
由單獨的過濾器定義。它為輸入顏色計算并返回經過過濾的 RGB 值。
清單 5.
CellularAutomataFilter
類(部分清單)
|
您可能會發現我沒有利用圖像中的像素與 CA 中的細胞之間的一對一映射關系。更確切地講,CA 是粗粒度的(至少大多數情況如此)。我最初這樣做是出于性能考慮。但是,使用不同大小的 CA 布局可以獲得有趣的像素效果。
清單 6 顯示了
getNewRGB(Color)
的一種特殊實現。它計算 “RGB 互補(complement)”,但這不是實際的顏色互補(計算真正顏色互補的過濾器也很有趣,但將其編寫成代碼則沒有這么簡單)。
清單 6.
RGBComplementFilter
類(部分清單)
|
我已經擴展
getNewRGB(Color)
,使其不僅可以傳入要轉換的像素顏色,而且可以傳入 8 個鄰居像素的顏色。這允許我創建某些效果,比如模糊效果或檢測邊緣,其中過濾的像素顏色取決于它的鄰居的顏色。這將是一個很好的增強功能。
最后,我將配合 CA 時鐘更新圖像來動畫圖像。為此,我使用了一個
javax.swing.Timer
(這是制作變化圖像動畫的簡單方式,但不是最好的方式。Jonathan Knudsen 的著作
Java 2D Graphics
提供了一種更好更復雜的方式來創建動畫。
圖 1 是 Georges Seurat 于 1884 年創作的點畫法名作 “A Sunday Afternoon on the Island of La Grand Jatte” 的照片:
圖 1. Georges Seurat 的 “A Sunday Afternoon on the Island of La Grand Jatte”
現在我將使用 RGB 互補過濾器在 Seurat 圖畫上運行 Seurat 應用程序。圖 2 顯示了過濾后的圖畫,此時循環空間處于它的初始隨機狀態:
圖 3 顯示了過濾后的圖畫,此時循環空間開始進入有序模式,但仍然帶有很大的隨機性:
圖 4 顯示了過濾圖畫的最終穩定狀態:
![]() |
|
不過,靜態圖片不能真正實現過濾器/CA(畢竟,這個應用程序是為 動畫 靜態圖像而編寫的)。我建議您運行實際的 Java applet 來查看運行中的過濾器/CA(請參閱 參考資料 ,獲得即時 demo 的鏈接)。
一 些人可能會認為在 “A Sunday Afternoon on the Island of La Grand Jatte” 之類的偉大作品上運行圖像過濾器應用程序是一種褻瀆。我當然很贊同此觀點。但我只是以這幅畫為例子。我的主要目標是展示如何使用一種簡單的細胞自動機器, 以有趣而復雜的方式來制作圖像動畫,以一副熟悉的名畫作為例子會比較好。
我曾在許多類型的畫上運行過 Seurat,在抽象藝術和具象藝術方面都得到了有趣的結果。但是,似乎在現代藝術 — 特別是流行藝術方面效果更好。例如,當您在 Jasper Johns 的 “Flag” 畫上運行 Seurat 時,會出現有趣的圖案。循環空間的對角線能根據 “Flag” 畫中的直線很好地工作。在 Jackson Pollock 的水滴畫中,運行 Seurat 時也會產生有趣的結果。例如,隨著循環空間 CA 越過 Pollock 的 “Blue Poles”,它會隱藏、顯示、再隱藏這幅復雜畫作的細節,讓您在不同時間集中注意不同的部位。這對照片同樣適用。我喜歡在 Ralph Eugene Meatyard 超現實主義的照片上運行 Seurat。
在運行 Seurat 這樣的應用程序時,您有 3 種選擇:2D 細胞自動機類型、過濾器和原始圖像。在這篇文章中,我只使用了循環空間,但是也可以使用其他類型的 2D 細胞自動機(如 Hodgepodge)。只要發揮您的想象力,就能編寫出各種過濾器程序。我主要實踐了操作顏色的過濾器,但更改圖像空間關系的過濾器也很有趣。例如,您 可以編寫一個歪曲圖像表面的過濾器程序,創建類似于披頭士的 Rubber Soul 專輯的封面那種效果。最后,您可以使用任意圖像,比如照片。對于給定的圖像,各種過濾器和 CA 類型的組合可以生成更好或更差的結果。我希望本文能鼓起您體驗的欲望。
我對 Julia Braswell 在視覺藝術方面的幫助表示衷心的感謝!
![]() |
||
![]() |
Paul Reiners 是一位 Sun 認證的 Java 程序員和開發人員。他參與開發了幾個開放源碼程序,包括 Automatous Monk、Twisted Life 和 Leipzig。Reiners 于 1991 年 5 月獲得伊利諾斯大學 Urbana-Champaign 分校的應用數學(計算理論)碩士學位。他目前住在明尼蘇達州,在業余時間喜歡演奏低音電吉他,并且是一個爵士樂隊的成員。 |
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
