使用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开发】。