纹理和可绘制对象

在 GTK4 中,我们一直在尝试寻找更好的图像数据解决方案。在 GTK3 中,我们为此使用的对象是 pixbufCairo surfaces。但是它们不再符合要求,所以现在我们有了 GdkTextureGdkPaintable

GdkTexture

GdkTextureGdkPixbuf 的替代品。为什么它更好?
首先,它简单得多。API 如下所示:

int gdk_texture_get_width (GdkTexture *texture);
int gdk_texture_get_height (GdkTexture *texture);

void gdk_texture_download (GdkTexture *texture,
                           guchar     *data,
                           gsize       stride);

因此,它是一个 2D 像素数组,如果需要,您可以下载像素。它也被保证是不可变的,所以像素永远不会改变。存在许多构造函数可以从文件、资源、数据或 pixbuf 创建纹理。

但是纹理和 pixbuf 之间最大的区别在于它们不暴露用于存储像素的内存。事实上,在调用 gdk_texture_download() 之前,这些数据甚至不需要存在。
这在 GL 纹理中使用。例如,GtkGLArea 小部件 使用此方法来传递数据。GStreamer 也期望以 GL 纹理的形式传递视频。

GdkPaintable

但有时,您会拥有比不可变的一堆像素更复杂的东西。例如,您可能有一个动画 GIF 或一个可缩放的 SVG。这就是 GdkPaintable 的用武之地。
抽象地说,GdkPaintable 是一个接口,用于知道如何在任何大小下渲染自身的对象。受 CSS 图像 的启发,它们可以选择性地提供 GTK 小部件可用于放置它们的固有尺寸信息。
因此,GdkPaintable 接口的核心是使可绘制对象渲染自身的函数,以及提供尺寸信息的 3 个函数

void gdk_paintable_snapshot (GdkPaintable *paintable,
                             GdkSnapshot  *snapshot,
                             double        width,
                             double        height);

int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable);
int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable);
double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable);

最重要的是,当可绘制对象的内容或大小更改时,它可以发出“invalidate-contents”和“invalidate-size”信号。

为了使其更加具体,让我们以一个可缩放的 SVG 为例:可绘制对象的实现将返回没有固有大小(这些尺寸函数的返回值 0 即可实现),并且每次绘制时,它都会以给定大小绘制自身像素精确。
或者以动画 GIF 为例:它会提供其像素大小作为其固有大小,并将动画的当前帧缩放到给定大小。并且每当应该显示动画的下一帧时,它都会发出“invalidate-size”信号。
最后但并非最不重要的一点是,GdkTexture 实现了此接口。

我们目前正在更改 GTK3 中接受 GdkPixbuf 的所有代码,现在接受 GdkPaintable。当然,GtkImage 小部件已经更改,拖放图标或 GtkAboutDialog 也已更改。存在实验性补丁,允许应用程序向 GTK CSS 引擎提供可绘制对象。

如果您现在将所有关于 GStreamer 可能提供由 GL 图像支持的纹理,并创建可以上传到 CSS 的动画的可绘制对象的信息放在一起,您也许可以 看到它的发展方向……

GTK+ 4 中的输入法

GTK 对可加载模块的支持可以追溯到很久以前,这就是为什么 GTK 有很多代码来处理 GTypeModules 和搜索路径等。很久以后,Alex 为 GVfs 重新审视了这个主题,并提出了扩展点和实现它们的 GIO 模块的概念。这是一个好得多的框架,而 GTK 4 是我们切换到使用它的绝佳机会。

GTK+ 4 中的更改

因此,我最近花了一些时间在 GTK 中的模块支持上。这里的主要变化如下:

  • 我们不再支持通用可加载模块。此功能的少数剩余用户之一是 libcanberra,我们将考虑直接在 GTK+ 中实现“事件声音”功能,而不是依赖于模块来实现它。如果您依赖加载 GTK+ 模块,请与我们讨论实现您正在做的事情的其他方法。
  • 打印后端现在使用名为“gtk-print-backend”的扩展点定义,该扩展点需要 GtkPrintBackend 类型。现有的打印后端已转换为实现此扩展点的 GIO 模块。由于我们从未支持过树外的打印后端,因此这不应影响其他人。
  • 输入法也使用名为“gtk-im-module”的扩展点定义,该扩展点需要 GtkIMContext 类型。我们已经删除了所有非平台 IM 模块,并将平台 IM 模块移动到 GTK+ 本身,同时还实现了扩展点。

适配现有的输入法

由于我们仍然支持树外的 IM 模块,我想利用这篇文章的剩余部分来简要概述 GTK+ 4 的树外 IM 模块必须如何编写。

有几个步骤可以将传统的基于 GTypeModule 的 IM 模块转换为新的扩展点。下面的示例代码取自 Broadway 输入法。

使用 G_DEFINE_DYNAMIC_TYPE

我们将从模块加载一个类型,而 G_DEFINE_DYNAMIC_TYPE 是定义此类类型的正确方法

G_DEFINE_DYNAMIC_TYPE (GtkIMContextBroadway,
                       gtk_im_context_broadway,
                       GTK_TYPE_IM_CONTEXT)

请注意,此宏定义了一个 gtk_im_context_broadway_register_type() 函数,我们将在下一步中使用它。

请注意,动态类型除了更常见的 class_init 之外,还需要有一个 class_finalize 函数,它可以是微不足道的

static void
gtk_im_context_broadway_class_finalize
               (GtkIMContextBroadwayClass *class)
{
}

实现 GIO 模块 API

为了用作 GIOModule,模块必须实现三个函数:g_io_module_load()、g_io_module_unload() 和 g_io_module_query()(严格来说,最后一个是可选的,但我们仍然在此处实现它)。

void
g_io_module_load (GIOModule *module)
{
  g_type_module_use (G_TYPE_MODULE (module));
  gtk_im_context_broadway_register_type  
                        (G_TYPE_MODULE (module));
  g_io_extension_point_implement
             (GTK_IM_MODULE_EXTENSION_POINT_NAME,
              GTK_TYPE_IM_CONTEXT_BROADWAY,
              "broadway",
              10);
 }
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
  char *eps[] = {
    GTK_IM_MODULE_EXTENSION_POINT_NAME,
    NULL
  };
  return g_strdupv (eps);
}

正确安装模块

GTK+ 仍然会在 $LIBDIR/gtk-4.0/immodules/ 中查找要加载的输入法,但 GIO 只会查找名称以 “lib” 开头的共享对象,因此请确保您遵循该约定。

调试

就这样!

现在 GTK+ 4 应该加载您的输入法,如果您使用 GTK_DEBUG=modules 运行 GTK+ 4 应用程序,您应该会在调试输出中看到您的模块显示出来。