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

接上

我們趁熱打鐵,緊接上一回的棋盤格繪製,來挖掘一些不同繪製思路,使用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開發文檔//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],到底分別代表畫布上哪個像素點的顏色呢?初學者首先要做的當然是找官網,官方的文檔是最好的教輔材料//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類型數值)。至於性能上的比較,我們回頭再談,這回就結束了,再見!

結果