Processing 網格紋理製作(棋盤格)使用pixel() set()畫素點繪製方式

EYE發表於2020-09-26

接上

我們趁熱打鐵,緊接上一回的棋盤格繪製,來挖掘一些不同繪製思路,使用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 網格紋理製作(棋盤格)使用pixel() set()畫素點繪製方式
之後又查閱了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型別數值)。至於效能上的比較,我們回頭再談,這回就結束了,再見!

結果

Processing 網格紋理製作(棋盤格)使用pixel() set()畫素點繪製方式

相關文章