玩转 Skia 的颜色

颜色是一个渲染引擎最基本的概念之一,Skia 为开发者处理颜色提供了一系列类型和工具。

Skia 项目中常用的颜色类型和工具,比如:如何使用 Skia 源码内置的预定义颜色、如何通过不同的方式设置颜色,如何混合两个颜色,如何格式化颜色等内容

预定义颜色

// 透明色
SkColor SK_ColorTRANSPARENT
// 黑色
SkColor SK_ColorBLACK      
// 深灰 dark gray(DKGRAY).
SkColor SK_ColorDKGRAY     
// 灰色
SkColor SK_ColorGRAY       
// 浅灰 ligth gray(LTGRAY)
SkColor SK_ColorLTGRAY     
// 白色
SkColor SK_ColorWHITE      
// 红色
SkColor SK_ColorRED        
// 绿色
SkColor SK_ColorGREEN      
// 蓝色
SkColor SK_ColorBLUE       
// 黄色
SkColor SK_ColorYELLOW     
// 青色
SkColor SK_ColorCYAN       
// 洋红
SkColor SK_ColorMAGENTA    

注意,这里定义的颜色并不都与 HTML、CSS 或 SVG 里定义的颜色相同,有些颜色是有区别得,比如 Skia 定义的灰色与 CSS 定义的灰色并不是同一个颜色值(略有差异,但差异不大)。

根据颜色名称获取颜色

根据颜色的名称获取具体的颜色值,如下代码所示:

// #include "include/utils/SkParse.h"
SkColor color;
SkParse::FindColor("gold", &color);
SkPaint paint;
paint.setColor(color);

SkParse 类型在 include/utils/SkParse.h 头文件中定义。

这个类型的静态方法:FindColor 用于根据颜色名称获取颜色值。

使用十六进制数字设置颜色

 SkColor 是一个无符号 32 位整型值,所以下面这种做法也是一种常见的颜色定义做法:

SkColor color = 0xFF123456;
SkPaint paint;
paint.setColor(0xFF123456);

上述代码中颜色值从左到右,0x 代表这是一个十六进制数值,

FF是颜色的透明度分量,FF代表不透明,00代表透明。

123456代表一个颜色值,这里使用的颜色值与 CSS/SVG 中使用的颜色值是一样的。

使用颜色分量设置颜色

除了以这种方式定义颜色之外,还可以用以下方式定义一个颜色,如下代码所示:

paint.setColor(SkColorSetARGB(255,11,22,33));

SkColorSetARGB 方法的第一个参数是颜色的透明度分量,第二个参数是颜色的红色分量,第三个参数是颜色的绿色分量,第四各参数为颜色的蓝色分量,四个参数的值均为 0 到 255 之间的值。

颜色计算

你可能觉得既然 SkColor 是 uint32_t 类型的值,那么几个颜色的平均值就这几个颜色值的和再除以颜色的数量呗?!

不是的!要计算颜色的平均值,就必须对颜色的每个通道分量计算平均值才能得到真正的颜色平均值

如下代码用于计算几个颜色的平均值:

void averageColor(SkCanvas* canvas) {
    SkColor colorArr[6]{ 0xFF123456,0xFF654321,0xFF789ABC,0xFFABC789,0xFFDEF123,0xFF123DEF };
    SkColor4f averageColor{ SkColors::kTransparent };
    for (size_t i = 0; i < 6; i++)
    {
        auto tempColor = SkColor4f::FromColor(colorArr[i]);
        averageColor.fA += tempColor.fA;
        averageColor.fR += tempColor.fR;
        averageColor.fG += tempColor.fG;
        averageColor.fB += tempColor.fB;
    }
    averageColor.fR /= 6;
    averageColor.fG /= 6;
    averageColor.fB /= 6;
    averageColor.fA /= 6;
    //auto color = averageColor.toSkColor();
    canvas->clear(averageColor);
}

上面的示例中,使用了新的颜色类型:SkColor4f

这个类型是一个对象类型,独立持有 A、R、G、B 四个通道分量的值。每个分量的值都是从 0 到 1 取值的小数。

有了这个颜色类型就可以根据不同的颜色分量计算颜色的值了。

用 SkColor4f::FromColor 方法把一个 SkColor 转型成 SkColor4f 对象。

用 SkColor4f 对象的 toSkColor 方法,把一个 SkColor4f 对象转型成 SkColor 值。

融合颜色

两个半透明颜色叠加会得到一个怎样的颜色呢?

这个颜色既不是两个颜色的平均值,也不是两个颜色的和。

最简单的办法就是创建一个仅有一个像素的画布,

然后在这个画布上先后绘制这两个半透明颜色,

最终这个点上是什么颜色,就是叠加后的颜色值,示例代码如下所示:

// #include "include/core/SkBitmap.h"

void colorOverlay(SkCanvas* canvas) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(1, 1);
    SkCanvas tempCanvas(bitmap);
    tempCanvas.drawColor(0x88DD3456);
    tempCanvas.drawColor(0x88654321);
    auto result = bitmap.getColor(0, 0);
    canvas->clear(result);
}

这段代码有两点值得注意:

  1. SkCanvas 对象是基于 SkBitmap 对象创建的 与 SkPixmap 相同,SkBitmap 对象也是一个用来描述图像的对象; 与 SkPixmap 不同的是 SkBitmap 则持有并负责管理图像的二进制数据。 所有使用 tempCanvas 绘制的内容,都将影响 bitmap 管理的像素数据。 这是迄今为止介绍的第三种创建SkCanvas对象的方法。 第一种方法是调用 SkSurface 对象的 getCanvas 方法获取的 SkCanvas 对象。 第二种方法是使用 SkCanvas 的静态方法 MakeRasterDirect ,基于像素数据创建SkCanvas对象。

  2. SkBitmap 对象的 getColor 方法用于获取指定位置的像素颜色,因为bitmap 对象仅有一个像素,所以此像素的位置就是(0,0)

颜色格式化

有的时候需要把颜色格式化成字符串供用户使用。如下代码演示了如何把一个颜色值格式化成 RGBA 字符串:

// #include <format>

auto color = SkColor4f::FromColor(0x99887766);
int R{ (int)(color.fR * 255) }, 
    G{ (int)(color.fG * 255) }, 
    B{ (int)(color.fB * 255) },
    A{ (int)(color.fA * 255) };
auto colorStr = std::format("RGBA: {},{},{},{}", R, G, B, A); // RGBA: 136,119,102,153

这段代码首先把一个 SkColor 值转型成 SkColor4f 对象。接着把 SkColor4f 对象的 R、G、B、A 分量从 01 的小数转型成 0255 的整数。

最后通过标准库的 std::format 把 4 个颜色分量转型成字符串。

最终得到的 colorStr 字符串的值是:RGBA: 136,119,102,153

接下来看如何把颜色值转型成HEX字符串,代码如下所示:

// #include <format>
// #include <sstream>

auto color = SkColor4f::FromColor(0x99887766);
int R{ (int)(color.fR * 255) }, 
    G{ (int)(color.fG * 255) }, 
    B{ (int)(color.fB * 255) },
    A{ (int)(color.fA * 255) };
std::stringstream ss;
ss << std::hex << ((R << 24) | (G << 16) | (B << 8) | A);;
std::wstring hex = ss.str();
std::transform(hex.begin(), hex.end(), hex.begin(), toupper);
colorStr = std::format("HEX: #{}", hex); //HEX: #88776699

这段代码使用字符串流的方式,把 SkColor4f 对象的 4 个颜色分量输入到 std::wstringstream 流中(注意分量值的移位操作)。

使用std::transform方法把生成的字符串转型成大写字符串。

最终得到的字符串的值为:HEX: #88776699

颜色空间

SkColorSpace用于表示颜色空间,颜色空间用于确定图像的颜色类型和透明度计算方式。

以SkImageInfo::MakeN32Premul(w, h)为例,它创建的 SkImageInfo 对象的颜色类型为:kBGRA_8888_SkColorType,

这个颜色类型表示每个像素由四个 8 位分量组成(R、G、B、A),每个分量的取值范围为从 0 到 255,

透明度计算方式为:kPremul_SkAlphaType,表示透明度分量预先与其他颜色分量(R、G、B)相乘。

Skia 在 include\core\SkColorType.h 头文件中定义了 SkColorType 枚举,

这个枚举类型中包含很多颜色类型,比如:kRGBA_8888_SkColorType、kBGRA_8888_SkColorType、kARGB_4444_SkColorType等。

Skia 在 include\core\SkAlphaType.h 头文件中定义了 SkAlphaType 枚举,用于表示不同的透明度分量计算方式。

如下是两种创建颜色空间的方式

sk_sp<SkColorSpace> srgbColorSpace = SkColorSpace::MakeSRGB();
sk_sp<SkColorSpace> linearSrgbColorSpace = SkColorSpace::MakeSRGBLinear();

无论使用上述代码中哪个颜色空间,示例的运行结果都没什么异样(肉眼看不出)。

如果你没能理解这部分的知识,那也并没什么大碍,等以后工作中需要用到时,再细细研究它们也不迟。

合成模式

有的时候需要在画布的同一块区域绘制不同的图像,这就需要考虑以什么合成方式绘制这些图像了,

Skia中定义了很多合成模式,如下所示(图像来源于 Skia 官网):

上图中src是源内容,指你正在绘制的内容,dst是目标内容,指已经绘制到画布上的内容。

当绘制几何图形时,透明的部分不会更改画布上几何图形之外的内容。这使得绘制操作更加高效,因为不需要处理透明度遮罩之外的像素。

当绘制图像数据时,透明度的处理方式可能会有所不同,因为图像数据通常包含每个像素的透明度信息。

在这种情况下,Skia 会根据 SkBlendMode 的设置来混合透明图像像素和目标像素(效率更低)。

如下代码演示了一种图像合成的方式。

void drawBlendMode(SkCanvas* canvas) {
    canvas->clear(0); //todo canvas->drawColor(0xFFDDDDDD);

    SkPaint paint;
    paint.setColor(0xFF00FFFF);
    auto rect1 = SkRect::MakeLTRB(60, h / 2 - 30, w - 60, h / 2 + 30);
    canvas->drawRect(rect1, paint); //目标内容

    paint.setColor(0xFFFFFF00);
    paint.setBlendMode(SkBlendMode::kSrcOut);
    auto rect2 = SkRect::MakeLTRB(w / 2 - 30, 60, w / 2 + 30, h - 60);
    canvas->drawRect(rect2, paint); //源内容
}

上述代码中 paint 对象的 setBlendMode 方法用于设置合成模式。

示例中设置的合成模式为 SkBlendMode::kSrcOut ,

此模式为将显示 源内容 不被 目标内容 覆盖的部分(覆盖的部分既不显示源内容也不显示目标内容)。

最终运行的效果如下图所示:

合成模式.png

如下代码演示了如何使用 SkBlendMode::kClear 模式擦除一个圆的中心区域部分:

void drawEraser(SkCanvas* canvas) {
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setColor(0xFF00FFFF);
    auto r = std::min(w/2-60, h/2-60);
    canvas->drawCircle(w/2,h/2,r,paint);
    paint.setBlendMode(SkBlendMode::kClear);
    canvas->drawRect(SkRect::MakeXYWH(w / 2 - 50, h / 2 - 50,100,100), paint);
}

最终运行的效果如下图所示:

合成模式擦除.png

如果要开发一款画图软件,使用 SkBlendMode::kClear 来实现橡皮擦功能。

颜色是绘图的基础,也是 Skia 的重要组成部分.

04-03
<think>好的,用户想了解Skia图形库的使用教程和API文档。首先,我需要整理已有的引用内容,看看里面有没有相关的信息。根据用户提供的引用,引用[1]提到了Skia项目的基本使用教程,包括目录结构、启动文件和配置文件的介绍。引用[2]、[3]、[4]则介绍了Skia的基本功能和项目地址,其中引用[4]还提到项目托管在https://skia.org,那里有详细文档和构建指南。 接下来,我需要确认这些信息是否足够回答用户的问题。用户需要教程和API文档,引用[1]中的使用教程可能不够详细,但结合引用[4]中的官方网站,可以建议用户访问官网获取更多资源。此外,引用[3]提到了中文使用手册,可能对中文用户有帮助。 另外,用户可能还需要安装和配置的步骤,但现有引用中没有详细说明,可能需要补充一些通用步骤,比如通过Git获取代码、构建工具的使用等。同时,可能需要提到社区资源如Stack Overflow和GitHub Issues作为支持渠道。 需要确保回答结构清晰,分步骤说明。首先介绍官方资源,然后是中文本地化资料,接着是安装配置,最后是开发资源和社区支持。同时,生成的相关问题需要围绕Skia的应用场景、核心功能、中文资料获取以及性能优化等方面。 最后检查是否符合用户的要求:使用中文回答,行内公式用$...$,独立公式用$$...$$(虽然这个问题可能不需要数学公式),引用标识正确添加。确认没有使用Markdown格式,语言口语化,自然分段。</think>Skia图形库使用教程与API文档信息整理如下: ### 一、官方资源 1. **核心文档入口** 访问[Skia官网](https://skia.org)可获取完整API文档、构建指南和示例代码。文档包含以下核心模块: - 画布(Canvas)的绘制操作 - 路径(Path)与几何变换 - 图像编解码器(Image Codecs) - 文本渲染与字体处理[^4] 2. **中文使用手册** 通过[GitCode镜像仓库](https://gitcode.com/gh_mirrors/sk/skia)可获取社区维护的中文手册,包含: - 图形上下文初始化配置 - 基础绘图单元使用示例 - 硬件加速设置指南[^3] 3. **项目结构解析** 项目目录包含: ``` include/ # 头文件目录 src/ # 核心实现 modules/ # 扩展模块 ``` 启动文件通常通过`SkSurface`创建绘图表面[^1] ### 二、开发准备 ```bash # 克隆仓库 git clone https://gitcode.com/gh_mirrors/skia/skia.git # 安装依赖 python tools/git-sync-deps # 编译示例 bin/gn gen out/Static --args='is_official_build=true' ninja -C out/Static ``` ### 三、基础API示例 ```cpp #include "SkCanvas.h" #include "SkGraphics.h" void draw(SkCanvas* canvas) { SkPaint paint; paint.setColor(SK_ColorRED); canvas->drawCircle(50, 50, 30, paint); // 绘制红色圆形 } ``` 该示例展示了基础图形绘制流程,通过$SkPaint$控制绘制样式[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liulun

如果文章真帮到了你,谢谢您打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值