接上
我們趁熱打鐵,緊接上一回的棋盤格繪製,來挖掘一些不同繪製思路,使用pixel()
函式來繪畫。這是一個以每個畫素點作為物件來繪製的思路,而不是以圖形的方式來填充。這就改變了繪畫思路。實際上,Processing有這樣的現成函式,使用x、y座標來定義視口內某個畫素點的顏色值,即set(x,y)
,反之獲取某個畫素點的顏色值get(x,y)
,你可以進傳送門get() set() 看一下官方給的解釋。而pixel()
是指定圖片的畫素序列中某個點的顏色值,意味著不能隨心所欲能將其顏色改變成你要的,需要通過讀取操作loadPixels()
和重新整理操作updatePixels()
才能正確操作。前者將當前的PGraphics一張圖的所有畫素點物件存入容器pixels[]之中,方可進行操作,後者是將pixels[]容器的數值對當前的PGraphics影像畫素作一更新。當然也和螢幕畫素密度屬性pixelDensity()
有關,具體參見pixels()。
第一步
因此,我們不妨先在空pde中做些測試。如下:
void setup() {
size(400, 400);
loadPixels();
pixels[0] = color(0);
updatePixels();
}
試著執行,發現並沒有報錯,說明pixels[]這個陣列容器是存在的,而且取到了index索引0的這個pixel變數。有沒有問題?有!真有,如果寫多了物件導向的程式,就會很敏感的想到問題,那就是這個pixels[]是誰的屬性?這者說針對的物件是誰?是在訪問哪張圖的畫素集合?帶著疑問,去官網找答案。。。(我是沒有見到相關說明,當然不排除自己的疏忽,按道理這是需要給開發者說明白的,有讀者細心找到的話告知一聲,感激不盡!)並沒有找到很理想的說明,但是在Processing開發文件http://processing.github.io/processing-javadocs/core/中我找到了答案。發現PApplet類中有:
之後又查閱了Processing原始碼。也就是說,Processing幫我們預設建立了一個PGraphics,所有的PApplet類中的諸多繪製方法都在調取內部這個“g”的相應方法進行繪製或是其他操作,然後在draw()
函式呼叫之後將g影像顯示在視窗上!上述測試程式碼應該這樣補全:
void setup() {
size(400, 400); //定義的該PApplet對於surface視口大小,
//並且也建立了一個內建PGraphics,命名為g,大小是(width,height),並做了初識化
g.loadPixels();
g.pixels[0] = color(0);
g.updatePixels();
}
如果是DIY的PGraphics,那會是下面的程式碼形式:
PGraphics mygraphics;
void setup() {
size(400, 400); //定義的該PApplet對於surface視口大小
mygraphics = createGraphics(width,height);//建立graphics,大小為width,height,畫布大小
mygraphics.beginDraw(); //這兩行實則在做PGraphics的pixels[]等屬性的初始化,
mygraphics.endDraw(); //如果不加,下面的pixels就獲取不到,會拋空指標異常
mygraphics.loadPixels();
mygraphics.pixels[0] = color(0);
mygraphics.updatePixels();
}
這樣的話,大家對PGraphics
和其pixels[]
應該有新的認識了。
言歸正傳
回過頭,想想究竟如何使用一個畫素陣列來控制這個畫布的圖形影像呢,比如pixels[30]和pixels[120],還有pixels[600],到底分別代表畫布上哪個畫素點的顏色呢?初學者首先要做的當然是找官網,官方的文件是最好的教輔材料https://processing.org/tutorials/pixels/,這個連結是tutorials欄目中對pixels以及image的詳細講解。嗯,這些內容不是針對processing的,而是所有數字影像處理所有軟硬體的,值得細細研讀。
我們會得出這樣的結論,使用pixels[]陣列操控畫布上的顏色值來繪製圖形影像的方法有諸多,有一種方式比較通用和方便,參考文件中的圖示:
可轉換成程式碼表示,即:
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x + y * width; //這是一個公式,以橫縱兩軸向進行遍歷,一位陣列帶被對映到二維陣列中的某個值,即圖中的某個畫素點,可用loc = x + y *width來表示
pixels[loc] = color(0); //那麼loc 這個索引號 代表的是 (x,y) 這一個畫素點
}
}
可以總結出規律,可用loc = x + y *width
來表示 座標(x,y)
上的點正是pixels[loc]
所對應的點,前提是以橫縱兩軸向座標點(畫素點數量)進行遍歷,一位陣列帶被對映到二維陣列中的某個值。有了這個演算法,那接下來的工作就能順利展開。
按照上篇的邏輯,要計算出迴圈變數的增幅,來控制方塊大小,在這個環境中就轉變成單位時間端需要運算多少次pixels[] = ?
這一語句。因為畫素偏移我們不用管了, for迴圈中x,y變數就已經在偏移。首先確定步長,每軸向多少步長?當然是increW,increH:
increW = width/WCOUNT;
increH = height/HCOUNT;
有了步長限制,pixels[] = ?
語句沒有定義呢。上文說到有畫rect的顏色區分,這裡同樣道理,也有定義畫素點的顏色區分,繪製方式來回切換,即:
int k= 0;
void draw()
{
k ++;
if(k % 2 == 0)
pixels[x + y * width] = color(0);
if(k % 2 != 0)
pixels[x + y * width] = color(255);
}
上文使用k開關變數(切換狀態)去做累加,判斷其奇偶性來達到切換語句執行的效果,這裡沿用。那什麼時候是到了切換狀態的時間點呢,對,上述的步長就能派上用場,每走increW或increH個步長,就要切換狀態,以increH為例,其邏輯用程式碼表示:
indexH ++ ; //這個東東是什麼?又來了個增幅?還是開關變數?其實它才是真正控制步長的變數。
if (indexH % increH == 0)//注意表示式,同樣做求餘運算,判斷是否取到0,如果是,則剛好index加了一個步長
{
indexH = 0; //當到了一個步長,那步長計數要歸零,重新計數(其實這裡可以省略,因為求餘演算法可以彌補,但是為了設計演算法方便,可以保留)
k ++;
}
因為是套在迴圈體裡的,indexH的建立,就為了讓其迴圈體中迴圈次數達到increH次數之後執行if()
語句,indexH作為計數器!同樣,在另外一個for中也得套一層計數,加if()
,即:
increW = width/WCOUNT;
increH = height/HCOUNT;
int k = 0;
int indexH = 0;
int indexW = 0;
for (int x = 0; x < width; x ++)
{
for (int y = 0; y < height; y ++)
{
if (k % 2 == 0)
{
pixels[x + y * width] = color(0);
} else
{
pixels[x + y * width] = color(255);
}
///////第一層邏輯控制,控制縱向繪製時每走步長數換一次K//////////
indexH ++ ;
if (indexH % increH == 0)
{
indexH = 0;
k ++;
}
///////第一層邏輯控制//////////
}
///////第二層邏輯控制,控制橫向繪製時每走步長數換一次K//////////
indexW ++ ;
if (indexW % increW == 0)
{
indexW = 0;
k ++;
}
///////第二層邏輯控制//////////
}
這樣,兩層邏輯控制k的變化走向,才能控制好對於畫素點的顏色。完整程式碼如下:
int increW;
int increH;
int WCOUNT = 10;
int HCOUNT = 10;
void settings() {
size(800, 800);
}
void setup() {
loadPixels();
increW = width/WCOUNT;
increH = height/HCOUNT;
int k = 0;
int indexH = 0;
int indexW = 0;
for (int x = 0; x < width; x ++)
{
for (int y = 0; y < height; y ++)
{
if (k % 2 == 0)
{
pixels[x + y * width] = color(0);
} else
{
pixels[x + y * width] = color(255);
}
indexH ++ ;
if (indexH % increH == 0)
{
indexH = 0;
k ++;
}
}
indexW ++ ;
if (indexW % increW == 0)
{
indexW = 0;
k ++;
}
}
updatePixels();
}
void draw() {
}
言外
做個思考題,如果是用set()
直接定義畫素點值呢。如果能融會貫通,那這個題目就很好解了,因為例子本身就以畫素點的形式在兩層for()迴圈中遍歷,因此,直接替換就可,同時對上述程式碼做些優化,如下:
int increW;
int increH;
int WCOUNT = 10;
int HCOUNT = 10;
void settings() {
size(800, 800);
}
void setup() {
//loadPixels();
increW = width/WCOUNT;
increH = height/HCOUNT;
int k = 0;
int indexH = 0;
int indexW = 0;
for (int x = 0; x < width; x ++)
{
for (int y = 0; y < height; y ++)
{
if (k % 2 == 0)
{
set(x, y, color(0));
} else
{
set(x, y, color(255));
}
indexH ++ ;
if (indexH % increH == 0)
k ++;
}
indexW ++ ;
if (indexW % increH == 0)
k ++;
}
//updatePixels();
}
void draw() {
}
注意,不再使用pixels[]
,則無需使用loadPixels()
和updatePixels()
,直接定義(x,y)
座標畫素點上的顏色值(set()
最後一個引數即為color型別數值)。至於效能上的比較,我們回頭再談,這回就結束了,再見!