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

时间: 2024-08-05 22:08:45

Skia往SkBitmap上绘图时画不出来的问题的相关文章

iOS:CALayer核心动画层上绘图

在CALayer上绘图: •要在CALayer上绘图,有两种方法: 1.创建一个CALayer的子类,然后覆盖drawInContext:方法,可以使用Quartz2D API在其中进行绘图 2.设置CALayer的delegate,然后让delegate实现drawLayer:inContext:方法进行绘图 •注意: –不能再将UIView设置为这个CALayer的delegate,因为UIView对象已经是内部层的delegate,再次设置会出问题 –无论使用哪种方法,都必须向层发送set

Delphi下OpenGL2d绘图(03)-画线

一.前言 画线与画点基本上代码是相同.区别在于glBegin()的参数.绘制的框架代码可以使用 Delphi下OpenGL2d绘图(01)-初始化 中的代码.修改的部份为 Draw 函数的内容. 二.画线 GL_LINES:把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段 GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段 GL_LINE_LOOP:绘制从第一个顶点到最后一

Delphi下OpenGL2d绘图(02)-画点

一.前言 图形的绘制可以使用glBegin().glEnd()之间完成,绘制的框架代码可以使用 Delphi下OpenGL2d绘图(01)-初始化 中的代码.修改的部份为 Draw 函数的内容. 二.画点 使用glPointSize 函数指定栅格化点的直径.默认为1.0,只在GL_POINTS下起作用,关于消锯齿等功能以后再研究.使用glBegin(GL_POINTS)告诉OpenGL画点,参数GL_POINTS表示点,还有其他参数,如画线GL_LINES等,具体可以参考OpenGL单元的源码.

Delphi下OpenGL2d绘图(04)-画四边形

一.前言 画四边形基本上与前几遍文字代码是相同.区别在于glBegin()的参数“GL_QUADS”.绘制的框架代码可以使用 Delphi下OpenGL2d绘图(01)-初始化 中的代码.修改的部份为 Draw 函数的内容. 二.画四边形 使用GL_QUADS:绘制由四个顶点组成的一组单独的四边形.顶点4n-3.4n-2.4n-1和4n定义了第n个四边形.总共绘制N/4个四边形.学画四边形是为了画位图做准备. 设置颜色: glColor3f(1, 0.5, 0); 可以设置四边形的颜色,参数为三

filebeat+nginx 绘图时url不能模糊搜索的问题

filebeat+nginx 绘图时url不能模糊搜索的问题 1.修改之前nginx 日志配置为 这里$request_time和$upstream_response_time打上引号是因为,如果取不到这个值,这个值就位 - ,logstash会报错,所以就由字符串再转为float类型 log_format json '{"time": "$time_iso8601", ' '"remote_addr": "$remote_addr&q

利用图形窗口分割法将极坐标方程:r=cos(θ/3)+1/9用四种绘图方式画在不同的窗口中

利用图形窗口分割法将极坐标方程:r=cos(θ/3)+1/9用四种绘图方式画在不同的窗口中. 解:MATLAB指令: theta=0:0.1:6*pi;rho=cos(theta/3)+1/9; >> polar(theta,rho) >> >> plot(theta,rho) >> semilogx(theta,rho) >> grid >> hist(rho,15) 结果分别如下图: 图1 图2 图3 图4

Extjs中用dwr实现文件上传时,fileuploadfield不能正常显示的问题

当使用DWR调用Extjs的fileuploadfield 来做文件上传时,不能实现上传操作,需要修改dwr对应jar包中的engine.js文件 将1808行 var clone =value.cloneNode(true); 1812行  value.parentNode.insertBefore(clone,value); 注释掉 通过  var file = dwr.util.getValue("uploadFile");  获取fileuploadfield 对应的值 版权声

kindeditor更改图片上传时网络图片的路径

当我们想要使用kindeditor的图片上传功能时,有两种选择图片方式,一种是本地选择,一种是在图片空间中选择,图片空间的默认地址是服务器上的/kindeditor/attached/image/下面. 如果想要改变这个路径,需要找到/kindeditor/php/file_manager_json.php这个文件,然后可以看到下面几行: //根目录路径,可以指定绝对路径,比如 /var/www/attached/ $root_path = $php_path . '../attached/';

使用Kindeditor的多文件(图片)上传时出现上传失败的解决办法/使用Flash上传多文件(图片)上传时上传失败的解决办法

近来用户反映希望我们把在线编辑器中的多图片上传功能实现,因为他们在编辑商品描述时经常会有一次上传多张图片的需求,如果要逐张选择的话效率很低,客户的需求就是我们的追求,很快我们就把完善功能排到了日程表中,要求尽快实现. 我们在项目中使用的在线编辑器是Kindeditor4.1.10,它们的多文件上传插件是使用Flash实现的,原本应该就是能使用的,但为什么老是显示上传失败的,百度了一下前人的经验和教训,出现这种情况,有两种可能:1)上传的目标文件夹没有写权限,导致上传的文件无法进行写操作,所以上传