Skia往SkBitmap上绘图时画不出来的问题

Posted foruok

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Skia往SkBitmap上绘图时画不出来的问题相关的知识,希望对你有一定的参考价值。

使用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。

有两种方法。

    1. 编译前修改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工程里设置这三个宏的值和编译时一致。

    1. 修改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开发】。

以上是关于Skia往SkBitmap上绘图时画不出来的问题的主要内容,如果未能解决你的问题,请参考以下文章

在 Skia 中,将 rgba 原始数据转换为 skBitmap

Skia SkCanvas 不能用 SkBitmap 构建

Skia 越野车颜色混合

Skia 和 Android Paint 绘图对象及其使用或文档

Skia深入分析

Skia深入分析