在PPAPI插件中使用Skia绘图
最后更新于:2022-04-01 16:03:05
[**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)一文介绍了Skia的编译,现在我们可以尝试在PPAPI插件中来使用Skia了。
> foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。
# Skia的关键类库
官网[https://skia.org/](https://skia.org/)上有文档,可以看。然后下载的源码,可以使用SourceInsight之类的工具来查看。具体不再细说,我这里只提用到的三个关键类:
- [SkPaint](https://skia.org/user/api/skpaint)
- [SkCanvas](https://skia.org/user/api/skcanvas)
- SkBitmap
SkCanvas是画布,你可以在它上面画任意的图元,比如矩形、圆型、文字、弧……
SkPaint则是工具箱,聚合了画笔、颜色等各种绘画时要用的配置选项。
SkBitmap代表了一块图像数据。
你调用SkCanvas的方法绘制图元,这些图元对应的数据一定要有个地方保存或显示,用于保存或显示图像数据的那个角色,被称为backends。SkCanvas的backends,有好几种,我们这个简单的示例ppapi_skia只用到了SkBitmap这种后端。
2D绘图类库的基本设计都差不多,我的《[**Qt on Android核心编程**](http://item.jd.com/11571639.html)》一书里对Qt绘图有介绍,两厢比对,就会发现设计得差不多。不过有一点不同,QPainter会保存你设置的绘图选项(颜色、画笔等),而SkCanvas则不会,需要自己设计数据结构和逻辑来保存。
# ppapi_skia项目
ppapi_skia项目基于ppapi_simple而来,请先参考[**PPAPI插件的绘图与输入事件处理**](http://blog.csdn.net/foruok/article/details/50499813)这篇文章。
不过为了方便调用Skia(C++类库),我这次用了cpp源文件,ppapi_skia.cpp。(吐槽下,VS2013对于C文件的即时提示太弱……)
我还改了ppapi_simple,使用了FlushCompletionCallback来优化绘图流程。
Skia默认编译出来的是静态库,对于一个简单的PPAPI+Skia插件,我不知道需要链接哪些lib,写好了代码,编译,根据undefined reference错误一通狂找,发现要链接下列lib:
~~~
skia_core.lib
skia_ports.lib
skia_utils.lib
skia_effects.lib
skia_skgpu.lib
skia_opts.lib
skia_opts_ssse3.lib
skia_opts_sse41.lib
skia_opts_avx.lib
skia_images.lib
skia_sfnt.lib
libetc1.lib
libSkKTX.lib
opengl32.lib
~~~
[**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)一文提到编译后的lib文件路径是:E:\sources\skia\out\Release。设置到项目的附加库目录列表中。skia还依赖其他一些库文件,在E:\sources\skia\out\Release\obj\gyp目录下,把这个目录添加到附加库目录列表里。
Skia需要OpenGL,我们得链接Opengl32.lib,Windows SDK里有。
skia编译时加了/MD参数,CEF编译时用的MT,如果你用PPAPI的C++接口,就会冲突了。要么改skia,要么改CEF。skia库小,改它吧。我还没找到怎么在生成构建文件时设置,最简单直接的,就是到E:\sources\skia\out\Release\obj\gyp这里,把*.ninja文件里的/MD都修改为/MT。然后重新编译。
如过用C接口,没关系,把PPAPI的代码生成选项里的运行库修改为MD即可。我偷懒,就用C接口,把ppapi_skia项目的运行库选项设置为了MD。
好,关于工程基本就这样了。
# 源码
分C++源码和HTML源码。
### C++代码
So,源码来了,基于C代码改过来,有点乱,不过可以说明用法。
~~~
/*
* Copyright (c) 2016 foruok@程序视界. All rights reserved.
* 2016-1-16, edited by foruok.
* 如需转载,请关注微信订阅号“程序视界”,回复foruok获取其联系方式
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <tchar.h>
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_module.h"
#include "ppapi/c/pp_rect.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb.h"
#include "ppapi/c/ppb_core.h"
#include "ppapi/c/ppb_graphics_2d.h"
#include "ppapi/c/ppb_image_data.h"
#include "ppapi/c/ppb_instance.h"
#include "ppapi/c/ppb_view.h"
#include "ppapi/c/ppp.h"
#include "ppapi/c/ppp_instance.h"
#include "ppapi/c/ppb_input_event.h"
#include "ppapi/c/ppp_input_event.h"
#include "SkPaint.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
PPB_GetInterface g_get_browser_interface = NULL;
const PPB_Core* g_core_interface;
const PPB_Graphics2D* g_graphics_2d_interface;
const PPB_ImageData* g_image_data_interface;
const PPB_Instance* g_instance_interface;
const PPB_View* g_view_interface;
const PPB_InputEvent *g_input_interface;
const PPB_MouseInputEvent *g_mouse_interface;
/* PPP_Instance implementation -----------------------------------------------*/
struct InstanceInfo {
PP_Instance pp_instance;
struct PP_Size last_size;
PP_Resource graphics;
PP_Resource image;
SkBitmap *bitmap;
SkCanvas *canvas;
int inFlush;
int requestPaint;
unsigned int colorIndex;
struct InstanceInfo* next;
};
/** Linked list of all live instances. */
struct InstanceInfo* all_instances = NULL;
/** Returns a refed resource corresponding to the created graphics 2d. */
PP_Resource MakeAndBindGraphics2D(PP_Instance instance,
const struct PP_Size* size) {
PP_Resource graphics;
graphics = g_graphics_2d_interface->Create(instance, size, PP_FALSE);
if (!graphics)
return 0;
if (!g_instance_interface->BindGraphics(instance, graphics)) {
g_core_interface->ReleaseResource(graphics);
return 0;
}
return graphics;
}
unsigned int g_colors[4] = { 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFEA00FF };
void DoPaint(struct InstanceInfo * instance);
void FlushCompletionCallback(void* user_data, int32_t result) {
struct InstanceInfo *inst = (struct InstanceInfo*)user_data;
inst->inFlush = 0;
if (inst->requestPaint)
{
inst->requestPaint = 0;
DoPaint(inst);
}
}
const char szHelloSkia[] = "Hello Skia in PPAPI";
void DoPaint(struct InstanceInfo * instance)
{
instance->inFlush = 1;
instance->colorIndex++;
if (instance->colorIndex >= sizeof(g_colors) / sizeof(g_colors[0])) instance->colorIndex = 0;
/* [2] Skia Paint * foruok */
instance->canvas->drawColor(g_colors[instance->colorIndex]);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(true);
paint.setLCDRenderText(true);
paint.setStrokeWidth(4);
instance->canvas->drawText(szHelloSkia, ARRAYSIZE(szHelloSkia) - 1, 20.0f, 20.0f, paint);
paint.setColor(0x80CCCCCC);
instance->canvas->drawCircle(150, 130, 100, paint);
/* Paint image to graphics 2d. */
g_graphics_2d_interface->ReplaceContents(instance->graphics, instance->image);
g_graphics_2d_interface->Flush(instance->graphics,
PP_MakeCompletionCallback(&FlushCompletionCallback, instance));
}
void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
/* Ensure the graphics 2d is ready. */
if (!instance->graphics) {
instance->graphics = MakeAndBindGraphics2D(instance->pp_instance, size);
if (!instance->graphics)
return;
}
/* Create image data to paint into. */
if (!instance->image)
{
instance->image = g_image_data_interface->Create(
instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE);
if (!instance->image)
return;
}
/* [1] setup SkCanvas's backends * foruok */
if (!instance->bitmap)
{
/* foruok * get image data from PPB interfaces */
struct PP_ImageDataDesc image_desc;
uint32_t* image_data;
g_image_data_interface->Describe(instance->image, &image_desc);
image_data = (uint32_t*)g_image_data_interface->Map(instance->image);
if (!image_data) {
g_core_interface->ReleaseResource(instance->image);
return;
}
/* foruok * bind PPAPI image data to SkBitmap */
SkImageInfo ii = SkImageInfo::Make(size->width, size->height,
kBGRA_8888_SkColorType, kPremul_SkAlphaType,
kLinear_SkColorProfileType);
instance->bitmap = new SkBitmap();
instance->bitmap->installPixels(ii, image_data, image_desc.stride);
if (!instance->canvas)
{
/* foruok * construct SkCanvas with SkBitmap backend */
instance->canvas = new SkCanvas(*instance->bitmap);
}
}
if (!instance->inFlush)
{
DoPaint(instance);
}
else
{
instance->requestPaint = 1;
}
}
/** Returns the info for the given instance, or NULL if it's not found. */
struct InstanceInfo* FindInstance(PP_Instance instance) {
struct InstanceInfo* cur = all_instances;
while (cur) {
if (cur->pp_instance == instance)
return cur;
cur = cur->next;
}
return NULL;
}
PP_Bool Instance_DidCreate(PP_Instance instance,
uint32_t argc,
const char* argn[],
const char* argv[]) {
struct InstanceInfo* info =
(struct InstanceInfo*)calloc(1, sizeof(struct InstanceInfo));
info->pp_instance = instance;
/* Insert into linked list of live instances. */
info->next = all_instances;
all_instances = info;
g_input_interface->RequestInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
g_input_interface->RequestFilteringInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
OutputDebugString(_T("Instance_DidCreate\r\n"));
return PP_TRUE;
}
void Instance_DidDestroy(PP_Instance instance) {
/* Find the matching item in the linked list, delete it, and patch the
* links.
*/
struct InstanceInfo** prev_ptr = &all_instances;
struct InstanceInfo* cur = all_instances;
while (cur) {
if (instance == cur->pp_instance) {
*prev_ptr = cur->next;
g_core_interface->ReleaseResource(cur->graphics);
free(cur);
return;
}
prev_ptr = &cur->next;
cur = cur->next;
}
}
void Instance_DidChangeView(PP_Instance pp_instance,
PP_Resource view) {
struct PP_Rect position;
struct InstanceInfo* info = FindInstance(pp_instance);
if (!info)
return;
if (g_view_interface->GetRect(view, &position) == PP_FALSE)
return;
if (info->last_size.width != position.size.width ||
info->last_size.height != position.size.height) {
/* Got a resize, repaint the plugin. */
Repaint(info, &position.size);
info->last_size.width = position.size.width;
info->last_size.height = position.size.height;
}
OutputDebugString(_T("Instance_DidChangeView\r\n"));
}
void Instance_DidChangeFocus(PP_Instance pp_instance, PP_Bool has_focus) {
}
PP_Bool Instance_HandleDocumentLoad(PP_Instance pp_instance,
PP_Resource pp_url_loader) {
return PP_FALSE;
}
static PPP_Instance instance_interface = {
&Instance_DidCreate,
&Instance_DidDestroy,
&Instance_DidChangeView,
&Instance_DidChangeFocus,
&Instance_HandleDocumentLoad
};
PP_Bool InputEvent_HandleInputEvent(PP_Instance instance, PP_Resource input_event)
{
struct PP_Point pt;
TCHAR szLog[512] = { 0 };
switch (g_input_interface->GetType(input_event))
{
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
pt = g_mouse_interface->GetPosition(input_event);
_stprintf_s(szLog, 512, _T("InputEvent_HandleInputEvent, mouse down at [%d, %d]\r\n"), pt.x, pt.y);
OutputDebugString(szLog);
break;
/*
case PP_INPUTEVENT_TYPE_MOUSEUP:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse up\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSEMOVE:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse move\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSEENTER:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse enter\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSELEAVE:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse leave\r\n"));
break;
*/
default:
return PP_FALSE;
}
struct InstanceInfo* info = FindInstance(instance);
if (info && info->last_size.width > 0)
{
Repaint(info, &info->last_size);
}
return PP_TRUE;
}
static PPP_InputEvent input_interface = {
&InputEvent_HandleInputEvent
};
/* Global entrypoints --------------------------------------------------------*/
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module,
PPB_GetInterface get_browser_interface) {
g_get_browser_interface = get_browser_interface;
g_core_interface = (const PPB_Core*)
get_browser_interface(PPB_CORE_INTERFACE);
g_instance_interface = (const PPB_Instance*)
get_browser_interface(PPB_INSTANCE_INTERFACE);
g_image_data_interface = (const PPB_ImageData*)
get_browser_interface(PPB_IMAGEDATA_INTERFACE);
g_graphics_2d_interface = (const PPB_Graphics2D*)
get_browser_interface(PPB_GRAPHICS_2D_INTERFACE);
g_view_interface = (const PPB_View*)
get_browser_interface(PPB_VIEW_INTERFACE);
g_input_interface = (const PPB_InputEvent*)get_browser_interface(PPB_INPUT_EVENT_INTERFACE);
g_mouse_interface = (const PPB_MouseInputEvent*)get_browser_interface(PPB_MOUSE_INPUT_EVENT_INTERFACE);
if (!g_core_interface || !g_instance_interface || !g_image_data_interface ||
!g_graphics_2d_interface || !g_view_interface ||
!g_input_interface || !g_mouse_interface)
return -1;
OutputDebugString(_T("PPP_InitializeModule\r\n"));
return PP_OK;
}
PP_EXPORT void PPP_ShutdownModule() {
}
PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, instance_interface\r\n"));
return &instance_interface;
}
else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, input_interface\r\n"));
return &input_interface;
}
return NULL;
}
~~~
上面代码里我标注了两处与Skia有关的改动,[1]和[2],也加了注释。另外就是使用FlushCompletionCallback来处理绘图和刷新,把ppapi_simple中的一些全局变量修改为与插件实例对象绑定的变量,这样可以支持一个网页里嵌入多个plugin了。
### HTML代码
ppapi_skia.html代码如下:
~~~
<!DOCTYPE html>
<html>
<!--
Copyright (c) 2016 foruok. All rights reserved.
如需转载,请关注微信订阅号“程序视界”,回复foruok获取其联系方式
-->
<head>
<title>PPAPI-SKIA</title>
</head>
<body>
<embed id="plugin" type="application/x-ppapi-skia" width="400px" height="300px">
</body>
</html>
~~~
### 运行效果
使用下面命令来运行:
~~~
cefsimple.exe --ppapi-out-of-process --register-pepper-plugins="E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32\Release\ppapi_skia.dll;application/x-ppapi-skia" --url=file:///E:/sources/CEF/2526/chromium/src/cef/binary_distrib/cef_binary_3.2526.1364.gf6bf57b_windows32/Release/ppapi_skia.html
~~~
效果如下:
![PPAPI-Skia](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac346903b.jpg "")
Ok,这个示例就这么着了。有时间我会用Skia+PPAPI的方式制作一个简单的涂鸦板插件。
其他参考文章:
- [**CEF Windows开发环境搭建**](http://blog.csdn.net/foruok/article/details/50468642)
- [**CEF加载PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485448)
- [**VS2013编译最简单的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)
- [**理解PPAPI的设计**](http://blog.csdn.net/foruok/article/details/50486788)
- [**PPAPI插件与浏览器的交互过程**](http://blog.csdn.net/foruok/article/details/50494061)
- [**Windows下从源码编译CEF**](http://blog.csdn.net/foruok/article/details/50498740)
- [**编译PPAPI的media_stream_video示例**](http://blog.csdn.net/foruok/article/details/50498873)
- [**PPAPI插件的绘图与输入事件处理**](http://blog.csdn.net/foruok/article/details/50499813)
- [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228)
- [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315)
- [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)