Processing中PImage類和loadImage()、createImage()函數的相關解析
- 2021 年 7 月 1 日
- 筆記
- Processing
聊一聊Processing中PImage類和loadImage()、createImage()函數。因為要借P5做多媒體創意展示,圖片是一個很重要的媒體。有必要就圖片的獲取和展放作總結。
首先
有一點需要先提出來,PGraphics
是繼承自PImage
的,看源碼:
public class PGraphics extends PImage implements PConstants {
...
}
因此,理論上所有的繪製函數其實它的繪製對象都是PImage,都在這張圖紙上呈現內容,即默認的PGraphics g
,可參考筆者另一篇:
//www.cnblogs.com/sharpeye/p/13734132.html
這就給我們一個參考,意思是PGraphics
和PImage
時常要考慮兩者互相轉換的問題。實際運行時也是如此。
其次
PImage類
中設有混合疊加的方法、圖片文件的IO方法,即保存讀取方法等,比較常用的是loadPixels()
、save()
、filter()
等,當然還有許多內部成員變數,比如format
width
height
pixels
等等。其中format
指的是顏色通道格式,比如有RGB
ARGB
ALPHA
等。save()
是可以保存帶有alpha通道的影像的。
PImage類要使用必須要new實例對象,一般的,無外乎是使用loadImage()
、createImage()
這兩個函數來獲得這一對象。如果要讀取一張現有的影像資訊,那麼就load
。
根據官網說明,loadImage()
有兩參數可供填寫,即:loadImage(filename, extension)
。
filename
指的是本地文件路徑或者url文件路徑。本地文件路徑可以是絕對地址也可以是相對地址。
extension
指的是指定文件後綴,即文件類型匹配。如果於真實文件類型不匹配也能讀進記憶體,不過通道數據未必能讀取,也就是alpha層的問題。文件類型共有幾類 “.tga”, “.jpg”, “.png”, “.jpeg”, “.gif”等,“.tif”無法讀取,P5自己的.tiff保存文件是可以的。請見下文例子:
PImage img1;
PImage img2;
PImage webImg;
void setup() {
size(500,500);
img1 = loadImage("mypic.png"); //讀取相對路徑下的文件,即pde根目錄下的文件,如果有data文件夾,則在此文件夾下尋找
img2 = loadImage("d://mypic.png");//讀取絕對路徑下的文件
String url = "//processing.org/img/processing-web.png";//讀取web互聯網上的文件,也可讀取區域網下的文件
webImg = loadImage(url, "png");//選定讀取的文件類型為png
}
void draw() {
background(0);
image(img1, 0, 0);
image(img2, 200, 0);
image(webImg, 0, 200);
}
會注意到,讀取本地的文件速度非常理想,但是互聯網上的文件會根據網路情況產生不少等待時間,給它看成是Processing的假死狀態,這是不希望看到的情況,如何來避免呢?有個函數官方給我們了—-requstImage()
。
這個函數就可以避免假死狀態,或者稱之為非阻塞式讀取,而傳統讀取是阻塞式的。實質上查閱源碼會看到它新建了一個thread
執行緒,因此,可以避免佔用主執行緒而耽誤往後的語句執行任務。程式碼如下:
/**
* ( begin auto-generated from requestImage.xml )
*
* This function load images on a separate thread so that your sketch does
* not freeze while images load during <b>setup()</b>. While the image is
* loading, its width and height will be 0. If an error occurs while
* loading the image, its width and height will be set to -1. You'll know
* when the image has loaded properly because its width and height will be
* greater than 0. Asynchronous image loading (particularly when
* downloading from a server) can dramatically improve performance.<br />
* <br/> <b>extension</b> parameter is used to determine the image type in
* cases where the image filename does not end with a proper extension.
* Specify the extension as the second parameter to <b>requestImage()</b>.
*
* ( end auto-generated )
*
* @webref image:loading_displaying
* @param filename name of the file to load, can be .gif, .jpg, .tga, or a handful of other image types depending on your platform
* @param extension the type of image to load, for example "png", "gif", "jpg"
* @see PImage
* @see PApplet#loadImage(String, String)
*/
public PImage requestImage(String filename, String extension) {
// Make sure saving to this file completes before trying to load it
// Has to be called on main thread, because P2D and P3D need GL functions
if (g != null) {
g.awaitAsyncSaveCompletion(filename);
}
PImage vessel = createImage(0, 0, ARGB);
// if the image loading thread pool hasn't been created, create it
if (requestImagePool == null) {
ThreadFactory factory = new ThreadFactory() {
public Thread newThread(Runnable r) {
return new Thread(r, REQUEST_IMAGE_THREAD_PREFIX);
}
};
requestImagePool = Executors.newFixedThreadPool(4, factory);
}
requestImagePool.execute(() -> {
PImage actual = loadImage(filename, extension);
// An error message should have already printed
if (actual == null) {
vessel.width = -1;
vessel.height = -1;
} else {
vessel.width = actual.width;
vessel.height = actual.height;
vessel.format = actual.format;
vessel.pixels = actual.pixels;
vessel.pixelWidth = actual.width;
vessel.pixelHeight = actual.height;
vessel.pixelDensity = 1;
}
});
return vessel;
}
能發現官方設立了一個執行緒池介面類ExecutorService
,而後新建執行緒池,每次讀取圖片都新建一個執行緒入執行緒池,統一管理,並用ExecutorService
實例的對象來execute
執行,從而獨立於主執行緒來執行任務。
再次
光有讀取還不完善,來看看自己生成圖片對象createImage()
。很清楚,三個參數
Parameters
w int: width in pixels 圖片對象寬度尺寸
h int: height in pixels 圖片對象高度尺寸
format int: Either RGB, ARGB, ALPHA (grayscale alpha channel) 圖片類型
官方介紹到,要注意使用規範性,如下:
PImage image = createImage(600,400,RGB); //正確的實例PImage寫法 其中圖片通道類型有RGB, ARGB, ALPHA
// *錯誤* PImage image = new PImage(600,400,RGB); //錯誤的寫法!!
實際上參照源碼,就是new PImage的方法實例:
public PImage createImage(int w, int h, int format) {
PImage image = new PImage(w, h, format);
image.parent = this; // make save() work
return image;
}
值得思考的是:
一、 PImage對象改變其像素無外乎要修改其pixels數組值,而不能再圖片上直接繪製圖形,如果要繪製則必須轉換成PGraphics
;
二、 如果要複製PImage以實例多個對象,可以使用copy()
或者clone()
,比如:
PImage op;
PImage op2;
try {
op = (PImage)img2.clone(); 使用clone方法 注意轉換類型
}
catch(Exception e) {
}
op2 = img2.copy(): 使用copy方法
三、 繪製在PGraphics
上的方法有三種,一種是image()
,一種是set()
,還有一種是手動以遍歷像素的形式來繪製渲染。如下:
image(img1, 100, 0);
image(img2, 200, 0);
image(webImg, 0, 200); //使用image方法
set(0,0,img2); //使用set方法
loadPixels();
img1.loadPixels();
for (int x = 0; x < img1.width; x++) {
for (int y = 0; y < img1.height; y++) {
color c = img1.pixels[y*img1.width + x];
if(alpha(c) == 0) continue; //alpha通道檢測
pixels[y*width + x] = c ; //相當於 g.pixels[]... 使用了pixels數組直接賦值
}
}
updatePixels();
//************************************// 另一種手動填充像素 set
img1.loadPixels();
for (int x = 0; x < img1.width; x++) {
for (int y = 0; y < img1.height; y++) {
color c = img1.pixels[y*img1.width + x];
//if(alpha(c) == 0) continue; 使用set方法 alpha通道檢測可跳過
set(x,y,c); //使用set方法 注意使用set方法跟updatePixels方法有衝突,這裡去掉其調用
}
}
//************************************//
最後
當然,在實際使用中,會用到大量技巧,比如image()
、set()
兩函數渲染圖片效率有出入,一般可以用set()
來提高渲染效率,但是無法進行矩陣變換等運算。再如,PImage中的save()
可以保存帶通道的圖片。這次就簡單做一總結,我們往後再細聊,感謝閱讀!!