顯示具有 Canvas 標籤的文章。 顯示所有文章
顯示具有 Canvas 標籤的文章。 顯示所有文章

2014年4月27日 星期日

Hardware Acceleration 的陷阱

最近寫了個程式,剛好要利用 Canvas 中的 clipPath() 函式來繪圖。結果是,不管我如何呼叫 Canvas.clipPath(),似乎一點作用也沒有。最後才發現,原來是 hardware acceleration (硬體加速) 這個系統功能搞的鬼。

Hardware acceleration 是從 Android 3.0 開始,Android 繪圖系統利用 GPU 支援的 OpenGL 渲染功能來加速 2D 圖像繪製的一種新功能。硬體加速?可以加速繪圖的速度,這聽起來似乎很美好。不過實際上,這個功能有很多的限制。例如,Canvas.clipPath() 在 API 17 之前就是不支援的。

這個連結中 列出所有硬體加速功能的限制。很複雜吧!更糟的是,只要你的 minSdkVersion 或是 targetSdkVersion 設定為 14 以上,這硬體加速功能,預設竟然是開啟的!

如果你的應用需要用到的 Canvas 的各式繪圖函式,而結果又出乎你預期時,我會建議你先關掉硬體加速功能,試試看是否因為這個原因所導致的。

要關閉硬體加速功能,最簡單的方法是加上 android:hardwareAccelerated="false" 這個屬性到 AndroidManifest.xml 中的 Application 標籤中。不過這個方法會讓你應用中的所有 View 的繪製都不允許硬體加速。這似乎有點極端,所以 Android 也提供 Window based 或 View based 的關閉方法。當你確定應用的不正常行為是因硬體加速功能所導致,這時你可以利用前述方法,將某些 Views 或 Windows 的硬體加速功能關掉即可。


繼續閱讀全文...

2010年3月24日 星期三

幫圖片加上影像特效 (IV) - 3D 投射效果

Create simple effects for your images (IV) - 3D Projection

在 Android SDK 中有兩種 Camera classes。一個是 android.hardware.Camera,這是用來操控相機功能的類別。另一個是 android.graphics.Camera,這是什麼東西?今天我們就要來研究一下,這個類別能幫我們做些什麼事。

android.graphics.Camera 是一個可以讓你將 2D 物件在 3D 空間中移動,並將在其移動後的結果,畫在螢幕上的類別。

聽起來很有趣,是嗎?例如,最上面那張美女圖,你可以看出來,他是將原本的圖,以 Y 軸為軸心,旋轉約 30 度後,所畫出來的結果。

打開 android.graphics.Camera 的說明文件,你會發現,這個文件中只有下面這些 APIs 列表,什麼說明都沒有。

不過,從這些 APIs 的名稱與參數,你應該可以猜出一些端倪。沒錯,這個 Camera class,其實是個 helper class。他提供一些 APIs ,讓你控制要如何在 3D 空間中移動,最後再產生出合適的 Matrix ,讓你套用到 Canvas 的座標體系上。關於 Matrix,我在 幫圖片加上影像特效 (I) - 陰影特效 已經介紹過他的強大,還不知道的,先讀一下這篇。

用個例子來解釋這個 Camera 類別的用法,是最適當的。底下這個簡單的程式片段的結果,就是最上面那張圖。其中的 m_bmp 就是原本的美女圖。

從這個範例程式中,我們知道,你可以利用 Camera.translate() 及 Camera.rotateXXX() 等 APIs 在 3D 空間中移動。其實,這些 APIs 就是在操作 Matrix 的內容。最後,你只要呼叫 Camera.getMatrix() 即可將這 Matrix 的內容抓出,並套到 Canvas 上。

在 Android ,要做出 3D 的效果,你當然可以用 OpenGL 的函式。不過,如果你需要更快的顯示速度,或者只是要做個簡單的 3D 特效,那千萬不要忘了這個 android.graphics.Camera。


繼續閱讀全文...

2010年1月25日 星期一

幫圖片加上影像特效 (III) - 負片與灰階效果

Create simple effects for your images (III) - Negative & Grayscale Effects

前兩次提過,簡單地利用 Matrix ,就可以輕易做出 陰影特效鏡像特效

這次,我將向大家分享,如何利用另一種 Matrix - ColorMatrix,來做出負片與灰階效果。

最上面那張圖的上半部,是原來的影像,一個美麗的地球。下半部的左邊,就是這地球影像的負片效果。右邊,則是灰階效果的呈現。

負片效果

負片效果的演算法,很簡單。就像底下這樣。

算法很簡單,可是在 Android 中要如何做到呢?難不成要用 Bitmap.getPixel()Bitmap.setPixel(),逐一針對每個像素來改他的顏色值?!

當然不是!這樣的做法,太沒效率,速度一定很慢。在 Android 中,針對這樣的需求,你可以透過 ColorMatrixColorFilter 來完成。在建構一個 ColorMatrixColorFilter 時,你得傳給他一個 ColorMatrix。因此,重點就在這個 ColorMatrix。

ColorMatrix 文件一開頭的註釋,就已經將他的功能講得很清楚。

讓我們看一下,這負片效果的原始碼,你會更加清楚。

將一個地球的圖片設給 m_bmp,透過上面這個程式範例,畫出來的樣子,就是一張具有負片效果的圖。

灰階效果

負片會了,灰階應該也很簡單了吧!我告訴你,要完成灰階效果,更簡單。ColorMatrix 都幫你將算法寫好了。你只要呼叫 ColorMatrix.setSaturation(0) 即可。

等等,學東西,可不能這樣就含混帶過。別忘了,Android 的原始碼都給你了,我們得利用他追根究柢,一探究竟才行。

打開 ColorMatrix.java 的原始碼,翻到 setSaturation()。這個函式的實現如下:

透過 android.graphics.ColorFilter 與 android.graphics.ColorMatrix,你還可以玩出更多的效果,別忘了 ApiDemos 的範例程式中,也有用到。

很多好東西,就在你身旁,千萬別捨近求遠了。


繼續閱讀全文...

2009年12月29日 星期二

幫圖片加上影像特效 (II) - 鏡像特效

Create simple effects for your images (II) - Reflection Effect

上次介紹過如何實現陰影特效,這次我將介紹如何實現 iMac 上常見的鏡像特效。

鏡像特效最近還蠻流行的,尤其在 HTC 的 Sense 介面上,常常都可以見到。最上面的那張圖,就是來自 HTC Sense 介面,所自帶瀏覽器程式中的『我的最愛』截圖。看到沒,加了個鏡像特效後,那感覺就很立體,感覺上好像是這些圖片擺立在一個玻璃桌面上。

我想你一定想知道,在 Android 上要幫圖片加上鏡像特效,會不會很麻煩?我告訴你,一點也不麻煩。和陰影特效相似,同樣地,只要幾行程式,就可以搞定。

因此,在開始看答案之前,我會建議你要先有 Photoshop 的使用經驗。想想,如果用 Photoshop 要幫圖片加上鏡像特效,要如何做?我想一般不外乎是先複製個圖片,並將其垂直翻轉,接著再對這翻轉的圖片,加個由灰到黑的漸層 mask 即可。

如果你沒 Photoshop 的使用經驗,自己上網找一下,應該不難找到說明,像 ,還有 都有。

好了,讓我們來看一下答案。底下就是幫圖片加上鏡像特效的程式範例。

_DrawMirror() 函式是關鍵。還記得我剛剛說,用 Photoshop 要如何做出鏡像特效?第一步是先畫出垂直翻轉的圖片。

Android 繪圖座標體系預設的原點在左上角,X 軸往右是越來越大的正值,而 Y 軸往下,則是越來越大的正值。要畫出垂直翻轉的圖片,其實也就是要垂直翻轉整個繪圖座標體系。在 Android 中,要如何做?答案就是 canvas.scale(1.0f, -1.0f)。很簡單吧,沒想到給 scale() 函式一個負值,就可以翻轉相對應的軸。

在 Photoshop 中,做鏡像特效的第二步是要對這翻轉的圖片,加個由灰到黑的漸層 mask。

在 Android 中,要畫漸層色,那就一定得用 LinearGradient 這個類別。至於要對背景圖加上個 mask,就請參考一下 Paint 的 setXfermode() 函式。_Init() 這個函式,就是負責生成一個由灰到黑漸層 mask 的 m_paint 物件。

將一個地球的圖片設給 m_dw,透過上面這個程式範例,畫出來的樣子,就像下面這樣。是不是感覺這地球,放在一塊玻璃桌面上?


繼續閱讀全文...

2009年12月15日 星期二

幫圖片加上影像特效 (I) - 陰影特效

Create simple effects for your images (I) - Shadow Effect

Android 從 1.0 開始,他提供給開發者的各式繪圖函式,就比同時期其他手機平台來的強大與好用。

有 Android 基礎的你,應該都知道,在 Android 中繪圖,必須得要透過 Canvas 這個物件。在 Canvas 中,我最欣賞的功能就是,他讓開發者可以透過 matrix,來操作整個繪圖座標體系的位移、旋轉、傾斜等等功能。另外就是 Android 的 2D 繪圖功能,也開始支援 alpha,alpha 也就是透明度,這是要做一張美圖時,不可或缺的功能。

有了 matrix,加上 alpha,再配合比 bitmap 更為抽象的 drawable 物件,那你要將圖形做各式各樣的變形,甚至於將上影像的特效,都將會是輕而易舉的事。回想在 Windwos 3.1 的那個年代,要做到我要介紹的這些特效,那真的很辛苦,你必須要將影像中的像素,一個一個抓出來,再透過一些影像處理的演算法,才有辦法做到。

我就舉兩個影像特效例子,來說明這 Android 的繪圖功能有多強大。首先,我先介紹陰影特效要如何做。

在寫地圖程式時,通常會加個 Overlay 物件在地圖上,用來標示某個景點的所在位置。要在地圖上畫個 Overlay,Overlay 這個類別已經提供一個很好用的 drawAt() 函式給你。你只要負責給他一個沒有陰影的 Drawable,他會自動幫你製造出陰影的效果。結果就像最上面這張圖一樣,每個圖案標示,都有個陰影,這讓這些標示,看起來就像立在地圖上一般。

drawAt() 這個函式的宣告如下:

從實際的使用中,我們可以發現當你傳入 false 給 shadow 時,他就會幫你製造出陰影的效果。

這個函式,怎麼這麼神奇!今天我將幫你們翻出他的原始碼,讓我們來看看這個 drawAt() 是如何實現的。

底下就是 drawAt() 的原始碼。你看,短短不到 20 行,就可以幫任何的圖片做出陰影的效果。

為了幫助大家理解他是如何做到的,我將他整理成下面的程式範例。

_DrawShadowImg() 就是負責製造出陰影效果的函式。

這畫出陰影的函式,主要就是運用 matrix 與 drawable 的 color filter 這兩個功能。首先是 m_dw.setColorFilter(),將整個圖片的顏色都轉成半透明的黑色。接著 canvas.translate() 將座標體系原點移到 nX, nY 的位置,而 canvas.skew() 與 canvas.scale() 則是將座標體系做個 x 軸方向的傾斜,與 y 軸方向的大小壓縮。最後,呼叫 m_dw.draw() 將圖片畫上,陰影就這麼產生出來了。

底下這張圖,就是上面這個程式片段的執行結果。

下回,我將介紹要如何實現 iMac 上常見的鏡像特效。


繼續閱讀全文...

2009年7月13日 星期一

Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1)

Android Source Code Internals - Alarm Clock, AnalogClock (1)

鬧鐘的原始程式碼在 這裡

PS: 請不要向我要原始碼,所有Android原始碼都可已自行下載,詳情請見 https://source.android.com/source/building.html

一執行這個鬧鐘程式,畫面上第一眼看到的,就是一個大大的時鐘。因此我們今天,就先從這個時鐘下手。讓我們一探究竟,看他是如何實現的。

要了解這個時鐘,是用什麼元件做出來的。有兩個方法,第一個就是用 SDK 內自帶的 Hierarchy Viewer 工具;另一個,就是直接觀察 res/layout 中的畫面設計檔。這次,我們直接用第二個方法。

翻開 AlarmClock/res/layout 目錄的內容,你會發現有好幾個 clock_xxx.xml 的檔案。打開其中的 clock_droid2.xml 檔。內容如下所示:

嗯,看起來,這個時鐘就是用 android.widget.AnalogClock 這個元件直接實現的。只要透過 android:dial, android:hand_hour, android:hand_minute 這三個 XML 屬性,指定鐘面,時針與分針的圖檔,一個完整的時鐘,就會顯現出來。最上方的圖片,就是這個時鐘顯示的樣子。

都不需要任何額外的設定,這個時鐘元件就會自己依照目前的時間,將時、分針顯示在正確的位置,那他是如何定時更新時、分針位置?另外,注意到了嗎,時、分針的圖檔,只有一份。從這就猜到,這個時鐘還會自動將時、分針圖旋轉至正確的角度,貼在螢幕上。讓我們來看看,這個 AnalogClock 元件是如何實現這些功能的。

打開 AnalogClock 元件原始碼,扣掉最前面的註解不算,整個原始碼也才不到 230 行。這麼少的程式碼,卻能將這個時鐘功能做得這麼完整。這也是我常最推薦,一定要看的元件原始碼之ㄧ。

這次,我們要看的重點有兩個。

  1. 在元件程式中,要如何隨時都知道時間的改變?
  2. 在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?

在元件程式中,要如何隨時都知道時間的改變?

原來在 onAttachedToWindow() 中,AnalogClock 透過 registerReceiver() 註冊了三個由系統發出的 Actions,ACTION_TIME_TICK, ACTION_TIME_CHANGEDACTION_TIMEZONE_CHANGED

在 Android 中,系統會透過 Intent 傳送某些系統通知訊息,這些通知訊息又稱為 Action。在 Intent 類別的說明中,你可以找到所有的 Action 定義。

在你的程式中,想要收到這些系統通知訊息,有兩個方法。第一個方法就是在 AndroidManifest.xml 中定義 receiver 的類別,這個方法在日後的介紹中會提到。第二種方法就是利用 registerReceiver(),向系統註冊接收的類別。由於 AnaloClock 是以元件的方式存在,如果每次使用這個元件,都還要在 AndroidManifest.xml 中定義,那就太麻煩了。因此, AnaloClock 採用的是第二種方法。

其中,ACTION_TIME_TICK,就是由系統在每分整,傳送出來的 Action。ACTION_TIME_CHANGED 則是當使用者更改系統時間時,所送出的 Action。當你的手機跨越時區,系統則會送出 ACTION_TIMEZONE_CHANGED 給有註冊的程式。

一但這些 Actions 發生時,他會呼叫在程式最後 new 出來的 BroadcastReceiver 物件, mIntentReceiver,中的 onReceive() 回呼函式。

首先,在 onReceive() 中,先檢查如果是時區有改變的話,當然你要重新設定 mCalendar 變數。

onTimeChanged() 函式,只是重新抓出最新的時、分資料。

在 onReceive() 的最後,再呼叫 View.invalidate()。這個動作,會觸發 View.onDraw() 函式的呼叫,並在螢幕上畫上最新的時,分針。

在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?

Android 的 2D 繪圖功能,其實是很強的,尤其是 matrix 這部份。透過 matrix,你就可以輕易地,控制整個繪圖座標體系的位移、旋轉、傾斜,放大等功能。舉個例子來說,如果你要將圖片旋轉 30 度後,再貼在螢幕上。傳統的作法,都是直接先將圖片的內容,逐點旋轉 30 度。但是在 Android 平台上,你不用這麼麻煩。你只要將整個座標系統轉個 30 度,之後不管你是畫線,還是貼圖,最後呈現在螢幕上的都是轉 30 度的效果。

想要了解 AnalogClock 是如何選轉時、分針圖片,那看 onDraw() 這個函式就對了。我將 onDraw() 中,和這部份選轉時、分針圖片相關的,列在下面。其中的 canvas.rotate() 是關鍵,這個函式就是在將座標系統轉個角度。看完這個例子,有沒有發覺要在 Android 上旋轉、放大或縮小圖片,其實都是只要兩三行程式,就可搞定的事。

AnalogClock 就剩 constructors,與其他 onMeasure(), onSizeChanged(), onDetachedFromWindow() 等函式還沒提到,我想這部份就留給你自己去發掘。


繼續閱讀全文...