编程透明和渐变输出而不使用 Cairo (C# X11) - 概念验证






4.69/5 (4投票s)
如何在 X11 窗口上使用 X11 的 (低级 API) XRender 协议扩展绘制透明和渐变输出。
引言
本文展示了如何使用 Mono Develop 从 C# 访问通过 Xlib/X11 协议的 XRender 扩展。许多 API 调用都已定义并经过测试,可以直接使用,并且解决了一些挑战,例如,定义背景位图或绘制到 Xt/Athena 小部件的前景。
由于 XRender 扩展基于 Xlib/X11 协议,因此也支持 Xlib/X11 窗口和 Xm/Motif 小部件。
本文旨在验证使用 C# 编程 XRender 扩展可以轻松实现(概念验证)。它提供了 32 位和 64 位示例应用程序的完整项目。
背景
这是探讨使用 Mono Develop 从 C# 调用 X* API 的系列文章中的第五篇。
第一篇文章是 使用 Mono Develop 编程 Xlib - 第 1 部分:低级 (概念验证),处理 Xlib/X11。第二篇文章是 使用 Mono Develop 编程 Xlib - 第 2 部分:Athena 小部件 (概念验证),处理 Xt/Athena。第三篇文章是 使用 Mono Develop 编程 Xlib - 第 3 部分:Motif 小部件 (概念验证),处理 Xm/Motif。第四篇文章是 使用 Mono Develop 编程 Xlib - 第 4 部分:FWF Xt 小部件,处理 Xfxf/Free Widget Foundation 小部件。当前文章继续探讨 Xlib/X11 协议的 XRender 扩展,并展示如何使用 C#/Mono 等现代语言/IDE 进行透明和渐变绘图。
尽管示例代码是为 Xt/Athena 小部件编写的,但它可以轻松移植到 Xlib/X11 窗口、Xm/Motif 小部件和 FWF Xt 小部件。
本文阐述了使用 C# 进行 XRender 扩展编程(使用透明和渐变 API 调用)是多么容易实现。由于用户期望具有吸引力设计的应用程序,基于 Xlib/X11 窗口、Xt/Athena 小部件、Xm/Motif 小部件和 FWF Xt 小部件的 GUI 变得乏味。它们缺乏透明、渐变和抗锯齿绘图功能。XRender 扩展支持所有这些功能,但本文仅介绍透明和渐变绘图(抗锯齿绘图是极其底层且复杂的使用方式 - 它应该由 freetype 库辅助或被 Xft 取代)。这些可能是将深受喜爱但陈旧的基于 Xlib/X11 窗口、Xt/Athena 小部件、Xm/Motif 小部件或 FWF Xt 小部件的应用程序升级到具有现代外观的 GUI 的两个重要原因。
我只想补充一点,大多数最新的 X11 窗口管理器、Cairo 库的 X11 后端(以及因此 GTK+ UI)、KDE(如果 Open GL 不可用)和许多其他库都使用 XRender 扩展,通常与 XDamage 扩展结合使用。
使用代码
该示例应用程序是在 使用 Mono Develop 编程 Xlib - 第 2 部分:Athena 小部件 (概念验证) 一文的示例应用程序基础上开发的,使用 Mono Develop 2.4.1 针对 Mono 2.8.1 在 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面环境下编写。无论是移植到任何旧版本还是新版本都不成问题。
示例应用程序的解决方案由四个项目组成(提供完整源代码下载)
- X11Wrapper 定义了 Xlib/X11 调用 libX11.so 的函数原型、结构和类型(已在本系列前几篇文章中介绍)
- XawNative 包含少量本地 C 代码,用于访问 Athena 小部件类记录,以及一个演示如何集成第三方 Xt 小部件的示例
- XtWrapper 定义了 Xt 调用 libXt.so 的函数原型、结构和类型,以及 Athena 小部件调用 libXawNative.so 的函数原型(已在本系列第二篇文章中介绍)
- XRenderExtension 包含基于 Xt/Athena 小部件的示例应用程序
该示例应用程序还通过了 Mono Develop 3.0.6 在 OPEN SUSE 12.3 Linux 64 位 DE 和 GNOME 桌面、IceWM、TWM 和 Xfce 上针对 Mono 3.0.4 的测试。
32 位和 64 位解决方案之间唯一的区别是一些类型的定义,正如本系列第一篇文章中已经描述的那样。
应用程序本身只有 952 行代码。一些通用 GUI 代码已发布到多个 Xt* 类中——不是为了用 C# 包装 Xt/Athena 小部件,而是为了组织可重用代码。Xt/Athena 的函数原型和一些必要的结构/枚举定义在 Xtlib 类中。应用程序显示
- 一个带有 3 个按钮的菜单栏,其中前两个按钮有下拉菜单,最后一个按钮有回调,
- 第一个菜单按钮带有一个包含三个条目的经典(Xt Release 4 之前)下拉菜单,第二个菜单按钮带有一个包含四个条目的 Xt Release 4 简单菜单,
- 一个标签小部件,用于显示文本输出以及透明和渐变功能,
- 一个带有透明度的应用程序图标(并非所有最新的窗口管理器都能显示它,但 Xfce 可以),
- 一个带有位图和标签的状态栏,
- 一个带有位图的独占抓取问题对话框,可以取消应用程序退出。
第一张图片显示了我的 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面可用的 X11 服务器扩展列表。从底部数第七个是 RENDER——本示例应用程序所利用的扩展。可以通过菜单 背景图片 | 无背景图片 调用此显示。
第二张图片显示了在 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面上的重叠透明矩形(红色、绿色和蓝色),它们将颜色值添加到黄色、青色、品红色和白色。可以通过菜单 背景图片 | 半透明矩形重叠 调用此显示。
第三张图片展示了在 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面上的堆叠透明矩形(从填满整个小部件的深红色开始,然后变为蓝色,同心圆地减小尺寸)。矩形颜色值的添加使外观从外部的深红色通过红色和品红色变为内部的白色。可以通过菜单 背景图片 | 半透明矩形堆叠 调用此显示。
第四张图片显示了在 OPEN SUSE 12.3 Linux 64 位 DE 和 Xfce 桌面上的一个垂直线性颜色渐变,从红色经绿色和蓝色回到红色。可以通过菜单 前景渐变 | 线性垂直颜色渐变 调用此显示。
第五张图片显示了在 OPEN SUSE 12.3 Linux 64 位 DE 和 Xfce 桌面上的一个中心锥形颜色渐变,从红色经绿色和蓝色回到红色。可以通过菜单 前景渐变 | 锥形颜色渐变 调用此显示。
最后一张图片显示了在 OPEN SUSE 12.3 Linux 64 位 DE 和 Xfce 桌面上的一个非中心径向颜色渐变,从红色(内部)经绿色和蓝色回到红色(外部)。可以通过菜单 前景渐变 | 径向颜色渐变 调用此显示。
通用功能
通用功能(菜单、输出区域、状态栏)源自 使用 Mono Develop 编程 Xlib - 第 2 部分:Athena 小部件 (概念验证) 文章的示例应用程序,并且大部分相同。通过转换/动作注册,为输出区域小部件的曝光事件添加了一个回调,以将渐变样本绘制到前景。
只有一个新的问题解决方案,它影响了作为 Xt/Athena Form 小部件实现的根布局管理器:Form 小部件提供了约束资源,可以将子元素锚定到彼此相对位置(fromHoriz
、fromVert
)或相对于它们所属的表单(left
、top
、right
、bottom
)。这允许控制子元素的位置,这对于 Xt/Athena Box 小部件是不可能的。
默认的约束资源是 left
、top
、right
和 bottom
,设置为 XawRubber
。如果布局计算基于 float
或 double
精度,这将是一个很好的解决方案。但它基于 int
,因此每次应用程序窗口的大小调整都会导致相对较小的部件变得更小(四舍五入效应),相对较大的部件变得更大(四舍五入效应)。这种行为迟早会完全破坏每个布局。
本示例应用程序通过为相对较小的小部件(例如 StatusIcon)明确设置初始宽度/高度,并将它们锚定到父级 Form 小部件的一个边界,并为相同方向的两个锚点(例如 left: ChainLeft
和 right: ChainLeft
)来防止此问题。这会强制相对较小的小部件具有固定大小,同时保持相对较大的小部件的橡皮筋行为。
透明功能
透明绘图功能基于在图片上绘图。图片是基于像素图创建的。
但首先要检查先决条件。
// Investigate installed X11 extensions *** without *** using any X11 extensions.
string listExtensions = String.Empty;
IntPtr pListExtensionNames = X11lib.XListExtensions (display, out listExtensions);
if (pListExtensionNames != IntPtr.Zero)
{
if (!string.IsNullOrEmpty (listExtensions))
{
Arg[] outputLabelArgs = { new Arg(XtNames.XtNlabel, listExtensions + "\0") };
Xtlib.XtSetValues (_output, outputLabelArgs, (XCardinal)1);
}
X11lib.XFreeExtensionList (pListExtensionNames);
}
// Investigate installed XRender extension's version *** without *** using XRender extensions.
TInt majorOpcode;
TInt firstEvent;
TInt firstError;
if (X11lib.XQueryExtension (display, "RENDER", out majorOpcode, out firstEvent, out firstError) != true)
{
Console.WriteLine ("ERROR: () - Call to XQueryExtension() for 'RENDER' failed.");
return;
}
// Optionally investigate whether render extension is available on display using XRender extensions,
// the first event number used by the extension (note that Render currently uses no events)
// and the first error number used by the extension.
if (XRenderLib.XRenderQueryExtension (display, out firstEvent, out firstError) != true)
{
Console.WriteLine ("ERROR: () - Call to XRenderQueryExtension() failed.");
return;
}
// Optionally investigate installed XRender extension's version using XRender extensions.
TInt majorVersion = 0;
TInt minorVersion = 0;
if (XRenderLib.XRenderQueryVersion (display, out majorVersion, out minorVersion) <= 0)
{
Console.WriteLine ("ERROR: () - Call to XRenderQueryVersion() failed.");
return;
}
if ((int)majorVersion > 0 || ((int)majorVersion == 0 || (int)minorVersion >= 7))
{ ;
// O.K. All functions, this sample is based on, are supported.
// - XRenderFillRectangle/XRenderFillRectangles
// - XRenderComposite
// - XRenderCreateLinearGradient/XRenderCreateConicalGradient/XRenderCreateRadialGradient
// - ...
}
else
{
Console.WriteLine ("ERROR: draw() - Outdated version of XRender extension. Test might failed.");
}
随后可以调查要绘制的图片所需的大小,并为图片分配一个像素图。
int width = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNwidth);
int height = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNheight);
int pixmapWidth = Math.Max (1, width ); // Math.Max (1, ((int)(width / 8)) * 8);
int pixmapHeight = Math.Max (1, height); // Math.Max (1, ((int)(height / 8)) * 8);
IntPtr pixmapBackground = X11lib.XCreatePixmap (display, Xtlib.XtWindow(_output),
(TUint)pixmapWidth, (TUint)pixmapHeight, (TUint)24);
if (pixmapBackground == IntPtr.Zero)
{
Console.WriteLine ("ERROR: () - Call to XCreatePixmap() failed.");
return;
}
// ******************************************************************************************************
// Attention: Fill memory (behind pixmap) with zero to ensure a defined start value for raster operations.
IntPtr pGC = X11lib.XCreateGC (display, pixmapBackground, (TUlong)0, IntPtr.Zero);
X11lib.XSetForeground (display, pGC,
X11lib.XBlackPixelOfScreen (X11lib.XScreenOfDisplay (display, (TInt)0)));
X11lib.XFillRectangle (display, pixmapBackground, pGC, 0, 0, pixmapWidth, pixmapHeight);
X11lib.XFreeGC (display, pGC);
// ******************************************************************************************************
成功实现透明绘图的第一个绊脚石是遗漏了像素图内存的准备工作。
现在可以准备图片格式并分配图片了。
XRenderLib.XRenderPictFormat xRenderPictFormat =
XRenderLib.XRenderFindVisualFormat (display, X11lib.XDefaultVisual (display, 0));
if (xRenderPictFormat.id != IntPtr.Zero)
{
XRenderLib.XRenderPictureAttributes pictureAttributes = new XRenderLib.XRenderPictureAttributes();
pictureAttributes.poly_edge = (TInt)X11.XRenderPolyEdge.PolyEdgeSmooth;
pictureAttributes.poly_mode = (TInt)X11.XRenderPolyEdge.PolyModeImprecise;
IntPtr picture = XRenderLib.XRenderCreatePicture (display, pixmapBackground, ref xRenderPictFormat,
XRenderCreatePictureValueMask.CPPolyEdge |
XRenderCreatePictureValueMask.CPPolyMode,
ref pictureAttributes);
if (picture != IntPtr.Zero)
{
成功透明绘图的第二个绊脚石是未采用预乘颜色分量。虽然 X11 XColor
结构使用 byte red
、byte green
和 byte blue
分量,但 XRender XRenderColor
结构的 ushort red
、ushort green
、ushort blue
和 ushort alpha
分量可以认为是 XColor
结构的分量乘以 alpha——就像
xRenderColor.red = (ushort)(((ushort) xColor.red) * (ushort)alpha)
TUshort alphaStep = (TUshort)0x00FF;
XRenderLib.XRenderColor colorR =
new XRenderLib.XRenderColor ((TUshort)(255 * (long)alphaStep), // premultilied red
(TUshort)( 0 * (long)alphaStep), // premultilied green
(TUshort)( 0 * (long)alphaStep), // premultilied blue
alphaStep);
XRenderLib.XRenderColor colorG =
new XRenderLib.XRenderColor ((TUshort)( 0 * (long)alphaStep), // premultilied red
(TUshort)(255 * (long)alphaStep), // premultilied green
(TUshort)( 0 * (long)alphaStep), // premultilied blue
alphaStep);
XRenderLib.XRenderColor colorB =
new XRenderLib.XRenderColor ((TUshort)( 0 * (long)alphaStep), // premultilied red
(TUshort)( 0 * (long)alphaStep), // premultilied green
(TUshort)(255 * (long)alphaStep), // premultilied blue
alphaStep);
XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorR,
(TInt)(0), (TInt)(0),
(TUint)(pixmapWidth), (TUint)(pixmapHeight / 3 + 30));
XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorG,
(TInt)(0), (TInt)(pixmapHeight / 3 - 10),
(TUint)(pixmapWidth / 2 + 20), (TUint)(pixmapHeight * 2/3 + 10));
XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorB,
(TInt)(pixmapWidth / 2 - 20), (TInt)(pixmapHeight / 3 - 10),
(TUint)(pixmapWidth / 2 + 20), (TUint)(pixmapHeight * 2/3 + 10));
最后,必须分配图片绘制到的像素图,并进行清理。
// Assign background pixmap.
X11lib.XSetWindowBackgroundPixmap (display, Xtlib.XtWindow(_output), pixmapBackground);
// Apply background pixmap - invalidate the background.
// X11lib.XClearWindow (display, Xtlib.XtWindow(_output)); // Doesn't provide invocation of Expose!
X11lib.XClearArea (display, Xtlib.XtWindow(_output), (TInt)0, (TInt)0,
(TUint)pixmapWidth, (TUint)pixmapHeight, true);
// Ensure processing.
X11lib.XFlush (display);
}
// Finished drawing operations - picture is obsolete.
XRenderLib.XRenderFreePicture(display, picture);
}
// Finished background pixmap handling - pixmap is obsolete.
X11lib.XFreePixmap (display, pixmapBackground);
渐变功能
渐变绘图功能基于使用栅格操作渲染当前图像和新图像的合成。当前图像可以是窗口内容或像素图,由目标图片表示,而新图像完全由 XRender API 隐藏,由渐变图片表示。
先决条件检查与透明绘图相同。
随后,可以调查渲染合成所需图片的大小,准备图片格式,并分配目标图片。
int width = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNwidth);
int height = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNheight);
int pixmapWidth = Math.Max (1, width ); // Math.Max (1, ((int)(width / 8)) * 8);
int pixmapHeight = Math.Max (1, height); // Math.Max (1, ((int)(height / 8)) * 8);
XRenderLib.XRenderPictFormat xRenderPictFormat = XRenderLib.XRenderFindVisualFormat (display, visual);
if (xRenderPictFormat.id != IntPtr.Zero)
{
XRenderLib.XRenderPictureAttributes pictureAttributes = new XRenderLib.XRenderPictureAttributes();
/* This is the target- (or destination-) drawable, derived from the window content. */
IntPtr destPict = XRenderLib.XRenderCreatePicture (display, window, ref xRenderPictFormat,
XRenderCreatePictureValueMask.CPNone,
ref pictureAttributes);
if (destPict != IntPtr.Zero)
{
现在可以准备渐变图片了。
X11.TInt[] aColorStops = new X11.TInt[4]; /* XFixed */
XRenderLib.XRenderColor[] aColorList = new XRenderLib.XRenderColor[4];
XRenderLib.XLinearGradient linearGradient = new XRenderLib.XLinearGradient ();
/* Offsets for color-stops have to be stated in normalized form,
** which means within the range of [0.0, 1.0f] */
aColorStops[0] = XRenderLib.XDoubleToFixed (0.0f);
aColorStops[1] = XRenderLib.XDoubleToFixed (0.33f);
aColorStops[2] = XRenderLib.XDoubleToFixed (0.66f);
aColorStops[3] = XRenderLib.XDoubleToFixed (1.0f);
/* There's nothing much to say about a XRenderColor, each
** R/G/B/A-component is an premultiplied unsigned int (16 bit) */
aColorList[0].red = (X11.TUshort)0xffff;
aColorList[0].green = (X11.TUshort)0x0000;
aColorList[0].blue = (X11.TUshort)0x0000;
aColorList[0].alpha = (X11.TUshort)0xffff;
aColorList[1].red = (X11.TUshort)0x0000;
aColorList[1].green = (X11.TUshort)0xffff;
aColorList[1].blue = (X11.TUshort)0x0000;
aColorList[1].alpha = (X11.TUshort)0xffff;
aColorList[2].red = (X11.TUshort)0x0000;
aColorList[2].green = (X11.TUshort)0x0000;
aColorList[2].blue = (X11.TUshort)0xffff;
aColorList[2].alpha = (X11.TUshort)0xffff;
aColorList[3].red = (X11.TUshort)0xffff;
aColorList[3].green = (X11.TUshort)0x0000;
aColorList[3].blue = (X11.TUshort)0x0000;
aColorList[3].alpha = (X11.TUshort)0xffff;
/* Coordinates for the start- and end-point of the linear gradient are
** in window-space, they are not normalized like in cairo... */
linearGradient.p1.x = XRenderLib.XDoubleToFixed (0.0f);
linearGradient.p1.y = XRenderLib.XDoubleToFixed (0.0f);
linearGradient.p2.x = XRenderLib.XDoubleToFixed (0.0f);
linearGradient.p2.y = XRenderLib.XDoubleToFixed ((double)pixmapHeight);
IntPtr gradientPict = XRenderLib.XRenderCreateConicalGradient (display, ref conicalGradient,
aColorStops,
aColorList, (X11.TInt)4);
最后,必须将渐变图片作为复合图像渲染到目标图片,并进行清理。
XRenderLib.XRenderComposite (display, X11.XRenderPictureOp.PictOpSrc,
gradientPict, IntPtr.Zero, destPict,
(X11.TInt)0, (X11.TInt)0,
(X11.TInt)0, (X11.TInt)0,
(X11.TInt)0, (X11.TInt)0,
(X11.TUint)pixmapWidth, (X11.TUint)pixmapHeight);
XRenderLib.XRenderFreePicture (display, destPict);
XRenderLib.XRenderFreePicture (display, gradientPict);
}
}
关注点
这个示例应用程序演示了,只需少量代码即可为基于 Xt/Athena 小部件集的应用程序提供透明和渐变绘图功能。这些代码可以无缝集成到典型的 Xt 程序结构中,并且可以轻松地应用于普通的 X11 应用程序和 Xm/Motif 应用程序。
历史
- 2015 年 7 月 2 日,这是关于从 C# 和 Mono Develop 调用 X11 API 的系列文章中的第五篇。下一篇文章可能会探讨 Xft 来绘制抗锯齿文本。