使用SkBitmap作為SkCanvas後端繪圖時畫不出來的問題
用預設條件在采用了Intel Pentium CPU的PC上編譯Skia(參見Windows下從源碼編譯Skia)後,采用SkBitmap作為SkCanvas的後端來繪圖時,遇到了奇怪問題:“無論畫什麼,跟沒畫一個樣”。
代碼如下:
SkImageInfo ii = SkImageInfo::Make(480, 320, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
SkCanvas canvas(bitmap);
經過試驗,發現采用下面的代碼可以繪制成功:
SkImageInfo ii = SkImageInfo::Make(480, 320, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
SkCanvas canvas(bitmap);
是以我覺得可能是顔色類型的問題。就一路跟下去,搞了很久也沒搞明白……SkCanvas的繪圖源碼,一層套一層,看起來比較艱辛……沒看進去,中間繞道通過将SkCanvas繪制好的SkBitmap的像素手動轉換為RGBA達到了目的。不過如果圖檔大,逐像素轉換就很慢……容易的路總是有後患……
SkBitmapDevice與kN32_SkColorType
骨頭還是得啃。我确認了使用SkBitmap作為SkCanvas的後端時,比如上面的代碼,實際上SkCanvas自己配置設定了一個SkBitmapDevice來作為繪圖裝置,繪圖操作會遞交給SkBitmapDevice來完成。
SkBitmapDevice在建立繪圖裝置時校驗了拿到的SkBitmap對象的SkImageInfo屬性,對其中的Alpha Type和Color Type做了檢查。
SkBitmapDevice(SkBitmapDevice.cpp)的構造函數内部會調用valid_for_bitmap_device(),而valid_for_bitmap_device()方法,根據傳入的SkImageInfo(就是建立SkBitmap時設定的那個SkImageInfo)做了過濾,隻針對kAlpha_8_SkColorType、kRGB_565_SkColorType、kN32_SkColorType三種顔色類别建立SkBitmapDevice,而kN32_SkColorType==kBGRA_8888_SkColorType是以,我設定顔色為kRGBA_8888_SkColorType時,建立出來的SkBitmapDevice是無效的,是以怎麼繪制都無效。
valid_for_bitmap_device()的代碼如下:
static bool valid_for_bitmap_device(const SkImageInfo& info,
SkAlphaType* newAlphaType) {
if (info.width() < 0 || info.height() < 0) {
return false;
}
// TODO: can we stop supporting kUnknown in SkBitmkapDevice?
if (kUnknown_SkColorType == info.colorType()) {
if (newAlphaType) {
*newAlphaType = kUnknown_SkAlphaType;
}
return true;
}
switch (info.alphaType()) {
case kPremul_SkAlphaType:
case kOpaque_SkAlphaType:
break;
default:
return false;
}
SkAlphaType canonicalAlphaType = info.alphaType();
switch (info.colorType()) {
case kAlpha_8_SkColorType:
break;
case kRGB_565_SkColorType:
canonicalAlphaType = kOpaque_SkAlphaType;
break;
case kN32_SkColorType:
break;
default:
return false;
}
if (newAlphaType) {
*newAlphaType = canonicalAlphaType;
}
return true;
}
另外根據上面的代碼,隻處理了kPremul_SkAlphaType、kOpaque_SkAlphaType兩種Alpha類型,是以我們給SkBitmap指定Alpha類型時,如果是其他的,也不能成功建立SkBitmapDevice。
現在來看為什麼預設編譯出來的kN32_SkColorType==kBGRA_8888_SkColorType。
kN32_SkColorType在SkImageInfo.h中定義:
enum SkColorType {
kUnknown_SkColorType,
kAlpha_8_SkColorType,
kRGB_565_SkColorType,
kARGB_4444_SkColorType,
kRGBA_8888_SkColorType,
kBGRA_8888_SkColorType,
kIndex_8_SkColorType,
kGray_8_SkColorType,
kLastEnum_SkColorType = kGray_8_SkColorType,
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
kN32_SkColorType = kBGRA_8888_SkColorType,
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
kN32_SkColorType = kRGBA_8888_SkColorType,
#else
#error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"
#endif
};
kN32_SkColorType實際上是根據位元組序決定的一個值,用到的宏SK_PMCOLOR_BYTE_ORDER在SkPostConfig.h中定義:
#ifdef SK_CPU_BENDIAN
# define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3) \
(SK_ ## C3 ## 32_SHIFT == 0 && \
SK_ ## C2 ## 32_SHIFT == 8 && \
SK_ ## C1 ## 32_SHIFT == 16 && \
SK_ ## C0 ## 32_SHIFT == 24)
#else
# define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3) \
(SK_ ## C0 ## 32_SHIFT == 0 && \
SK_ ## C1 ## 32_SHIFT == 8 && \
SK_ ## C2 ## 32_SHIFT == 16 && \
SK_ ## C3 ## 32_SHIFT == 24)
#endif
因為我的主機是小端位元組序,是以SK_PMCOLOR_BYTE_ORDER是:
# define SK_PMCOLOR_BYTE_ORDER(C0, C1, C2, C3) \
(SK_ ## C0 ## 32_SHIFT == 0 && \
SK_ ## C1 ## 32_SHIFT == 8 && \
SK_ ## C2 ## 32_SHIFT == 16 && \
SK_ ## C3 ## 32_SHIFT == 24)
這個宏又用到了SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT這三個宏。分析SkPostConfig.h可知,預設編譯時,這三個宏的值在這裡定義:
#ifdef SK_BUILD_FOR_WIN
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# define WIN32_IS_MEAN_WAS_LOCALLY_DEFINED
# endif
# ifndef NOMINMAX
# define NOMINMAX
# define NOMINMAX_WAS_LOCALLY_DEFINED
# endif
#
# include <windows.h>
#
# ifdef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED
# undef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED
# undef WIN32_LEAN_AND_MEAN
# endif
# ifdef NOMINMAX_WAS_LOCALLY_DEFINED
# undef NOMINMAX_WAS_LOCALLY_DEFINED
# undef NOMINMAX
# endif
#
# ifndef SK_A32_SHIFT
# define SK_A32_SHIFT 24
# define SK_R32_SHIFT 16
# define SK_G32_SHIFT 8
# define SK_B32_SHIFT 0
# endif
#
#endif
SK_A32_SHIFT=24、SK_R32_SHIFT=16、SK_G32_SHIFT=8、SK_B32_SHIFT=0,是以,SkImageInfo.h中,SK_PMCOLOR_BYTE_ORDER(B,G,R,A)展開後如下:
SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && SK_A32_SHIFT == 24
這個表達式的值是 true ,是以kN32_SkColorType==kBGRA_8888_SkColorType。
我想用kRGBA_8888_SkColorType,需要在編譯時修改SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT這三個宏的值為下面的樣子:
SK_A32_SHIFT=24
SK_B32_SHIFT=16
SK_G32_SHIFT=8
SK_R32_SHIFT=0
這樣編譯出來的庫,kN32_SkColorType==kRGBA_8888_SkColorType。
有兩種方法。
-
- 編譯前修改CFLAGS等變量
生成ninja編譯腳本前,在cmd.exe裡執行下面的指令即可:
set "CFLAGS=-DSK_A32_SHIFT=24 -DSK_B32_SHIFT=16 -DSK_G32_SHIFT=8 -DSK_R32_SHIFT=0"
set "CPPFLAGS=-DSK_A32_SHIFT=24 -DSK_B32_SHIFT=16 -DSK_G32_SHIFT=8 -DSK_R32_SHIFT=0"
set "CXXFLAGS=-DSK_A32_SHIFT=24 -DSK_B32_SHIFT=16 -DSK_G32_SHIFT=8 -DSK_R32_SHIFT=0"
這樣編譯後,SkBitmapDevice在校驗Color Type時,kN32_SkColorType==kRGBA_8888_SkColorType,我們傳遞的SkBitmap的SkImageInfo的顔色類型為kRGBA_8888_SkColorType,就能成功建立繪圖裝置。不過,要使用kBGRA_8888_SkColorType的顔色類型的SkBitmap作為SkCanvas的後端繪圖裝置就不行了。
還有一點要注意:因為編譯時通過CFLAGS傳遞的SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT三個宏的值,并沒有被記錄到頭檔案裡,是以使用Skia的頭檔案時,檢測出來的SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT還是錯的,kN32_SkColorType也是錯的哈。要想避免有隐患,請在VS工程裡設定這三個宏的值和編譯時一緻。
-
- 修改SkUserConfig.h
如果不想在編譯時通過環境變量修改SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT,也可以修改SkUserConfig.h(在include\config目錄内),這樣的話,改動也被記錄下來了,因為SkTypes.h順序包含了SkPreConfig.h、SkUserConfig.h、SkPostConfig.h,改動在哪裡都是有效的,還省去了設定VS工程。SkUserConfig.h中有對這個檔案的說明。
解碼圖檔時Red與Blue通道反了
解決了問題之後,新的問題又來:解碼圖檔時Red與Blue通道反了。
(⊙o⊙)…還得繼續戰鬥啊。
硬着頭皮讀了半天源碼,以png為例,楞沒發現怎麼回事兒。後來都想用下面的方法繞過去了:把解碼出來的圖檔資料逐像素R、B交換。
總是想走容易的路。
再後來使勁實驗,去看SkImageDecoder.cpp、SkImageDecoder_libpng.cpp,漫天添加日志資訊檢視圖檔解碼器的建立流程,花了一天多時間,終于明白了問題在那裡(參見Skia圖檔編解碼子產品分析):
原來雖然我定義了SK_A32_SHIFT、SK_R32_SHIFT、SK_G32_SHIFT、SK_B32_SHIFT,把kN32_SkColorType調整過來了,讓SkBitmapDevice能建立kRGBA_8888_SkColorType格式的繪圖裝置了,但上面三個宏,并不能影響圖檔解碼,因為解碼器用的是Windows平台的COM元件,不受這三個宏影響,預設解碼出來的就是BGRA(參見SkImageDecoder_WIC::decodeStream方法)!
是以,隻好分析解碼器選擇流程,才有了Skia圖檔編解碼子產品分析,發現png、gif、jpeg等根本沒編譯。那麼把它們編譯進去就好了。
有兩個辦法:
- 直接修改out\Release\obj\gyp\images.ninja檔案,添加相關解碼器的cpp檔案
- 修改skia\gyp\images.gyp,把win平台下的條件編譯改一下
我用的第一種,硬把png等加上了,遇到各種錯,再改,好歹過來了……skia\gyp\images.gyp中說編譯順序會影響解碼器選擇,是以,加進去也不行,最後隻好直接調用CreatePNGImageDecoder之類的方法,沒用SkImageDecoder::DecodeXXX了。
現在看第二種應該更好些,Skia建構系統會自己來處理各種依賴,還能使用統一的SkImageDecoder接口,不過會因為個人的特殊需求污染Skia的編譯腳本……
就這樣吧。
其他參考文章詳見我的專欄:【CEF與PPAPI開發】。