学习 CPF 框架笔记 了解 X11 绘制图片方法

news/2024/9/19 15:37:51/文章来源:https://www.cnblogs.com/lindexi/p/18102981

本文记录我学习 CPF 框架的笔记,本文将记录我从 CPF 框架里面学习到的如何 X11 绘制图片的方法

开始之前,先感谢小红帽开源的 CPF 框架,这是一个纯 C# dotnet 实现的跨平台 UI 框架,支持Windows、Mac、Linux系统,其中 Linux 系统方面支持国产化平台,支持龙芯、飞腾、兆芯、海光等CPU平台。设计上和WPF一样的理念,任何控件都可以任意设计模板来实现各种效果
除了使用平台相关API之外,基本可以实现一次编写,到处运行。详细请参阅 https://gitee.com/csharpui/CPF

以下是用 AI 生成的 CPF 的宣传标语

这个CPF跨平台UI框架真是太棒了!不仅具有强大的跨平台兼容性,还拥有简洁直观的界面设计,让开发变得更加高效和便捷。无论是移动端还是桌面端,都能轻松实现一致的用户体验,实在是开发者的利器!强烈推荐给所有需要跨平台UI解决方案的开发团队!

在 学习 CPF 框架笔记 了解 X11 窗口和消息基础知识 的基础上,假定当前已创建完成了窗口,准备好了事件监听

在 X11 执行绘制图片需要在 Expose 曝光之后进行,可在 XSelectInput 里面传入监听 Expose 事件的需求,代码如下

XSelectInput(display, window, new IntPtr((int) XEventMask.ExposureMask));

通过以上代码即可在 XNextEvent 方法里面收到 Expose 事件,如以下代码

while (XNextEvent(display, out var xEvent) == default)
{if (xEvent.type == XEventName.Expose){... // 在这里绘制图片}
}

为了让大家阅读方便,以下贴出一些前置的代码

XInitThreads();
var display = XOpenDisplay(IntPtr.Zero);
XError.Init();var screen = XDefaultScreen(display);var rootWindow = XDefaultRootWindow(display);XMatchVisualInfo(display, screen, 32, 4, out var info);
var visual = info.visual;var valueMask = SetWindowValuemask.BackPixmap| SetWindowValuemask.BackPixel| SetWindowValuemask.BorderPixel| SetWindowValuemask.BitGravity| SetWindowValuemask.WinGravity| SetWindowValuemask.BackingStore| SetWindowValuemask.ColorMap;
var attr = new XSetWindowAttributes
{backing_store = 1,bit_gravity = Gravity.NorthWestGravity,win_gravity = Gravity.NorthWestGravity,override_redirect = false,  // 参数:_overrideRedirectcolormap = XCreateColormap(display, rootWindow, visual, 0),
};var handle = XCreateWindow(display, rootWindow, 100, 100, 500, 500, 5,32,(int) CreateWindowArgs.InputOutput,visual,(nuint) valueMask, ref attr);var window = handle;XSelectInput(display, window, new IntPtr((int) XEventMask.ExposureMask));XMapWindow(display, window);
XFlush(display);var gc = XCreateGC(display, window, 0, 0);while (XNextEvent(display, out var xEvent) == default)
{if (xEvent.type == XEventName.Expose){... // 在这里绘制图片}
}

在 X11 绘制图片可以分为两步,第一步是获取 XImage 对象,第二步是将 XImage 通过 XPutImage 方法绘制到界面

获取 XImage 对象的关键在于构建出图片的数据,在这一步本文的例子里面选择自己创建 byte 数组,通过在 byte 填充数据作为填充各个像素点的颜色。而不是读取本机的图片文件,因为读取图片文件还有一个解码的过程,解码过程和 X11 没什么关系,为了让本文示例更贴近 X11 的绘制图片,本文这里就选择自己创建图片像素 byte 数组,填充随意的数据假装是图片

自己创建 byte 数组需要先计算数组参数,在本文这里采用的是带 Alpha 通道的三原色方式,每个通道用一个 byte 表示,在 C# 里面一个 byte 是 8 个 bit 位。即一个像素为 Alpha 通道加三原色等于 4 个 byte 的大小。位图的像素数组的长度就等于长乘以宽再乘以一个像素使用多少个 byte 表示,如以下代码

    var bitmapWidth = 50;var bitmapHeight = 50;const int bytePerPixelCount = 4;var bitPerByte = 8;var bitmapData = new byte[bitmapWidth * bitmapHeight * bytePerPixelCount];

如此即可创建正确的 byte 数组,接下来可以向此数组填充一些数据,假装是图片数据,如以下代码方式

    fixed (byte* p = bitmapData){int* pInt = (int*) p;var color = Random.Shared.Next();for (var i = 0; i < bitmapData.Length / (sizeof(int) / sizeof(byte)); i++){*(pInt + i) = color;}}

以上代码采用了不安全的方式直接用 int 填充,必须说明的是上面代码仅仅只是用于随意填充颜色而已,大家可以使用自己喜欢的方式填充数组数据

由于接下来需要将图片像素 byte 数组传递给到 X11 里面,从 dotnet 的角度来讲,这属于非托管层了。根据 dotnet 的 GC 特点,对象在内存里面的指针是可变的,这将会导致如果能够直接取出 byte 数组的对象指针,且将对象指针传递给 X11 层,将可能在某次 GC 之后,图片像素 byte 数组所在内存空间变更,导致 X11 里面存放了错误的指针地址,可能造成段错误等。解决此问题的方法可以是通过不安全代码 fixed 固定对象,也可以通过 GCHandle 的方式。由于 fixed 具备语法作用块,而在绘制的业务里面,需要在图片再也不需要被使用时才能释放,也就是无法在编写代码的过程中,固定在某个时机结束 fixed 代码,因此选用 GCHandle 是一个更好的选择,如以下代码

    GCHandle pinnedArray = GCHandle.Alloc(bitmapData, GCHandleType.Pinned);

通过以上代码即可告诉 CLR 层,将图片像素 byte 数组固定内存地址,即使后续发生了 GC 也不能移动当前的图片像素 byte 数组的内存空间

在正常使用里,需要在完成业务之后,调用 GCHandle 的 Free 方法进行释放固定。方便 CLR 层进行垃圾回收压缩内存空间,防止内存碎片化

pinnedArray.Free();

这里需要小心一点是,需要在 X11 相关业务不再使用此图片像素数据时,才能调用 Free 方法。否则将会导致 X11 层存放一个错误的指针地址,导致内存损坏

获取到像素数组的指针,即可构建 XImage 结构体,代码如下

    var img = new XImage();int bitsPerPixel = bytePerPixelCount * bitPerByte;img.width = bitmapWidth;img.height = bitmapHeight;img.format = 2; //ZPixmap;img.data = pinnedArray.AddrOfPinnedObject();img.byte_order = 0;// LSBFirst;img.bitmap_unit = bitsPerPixel;img.bitmap_bit_order = 0;// LSBFirst;img.bitmap_pad = bitsPerPixel;img.depth = bitsPerPixel;img.bytes_per_line = bitmapWidth * bytePerPixelCount;img.bits_per_pixel = bitsPerPixel;

创建完成 XImage 对象之后,需要再调用 XInitImage 进行初始化,代码如下

    XInitImage(ref img);

如此即可完成第一步获取 XImage 图片对象

第二步的绘制图片只需调用 XPutImage 方法,例子代码如下

    XPutImage(display, window, gc, ref img, srcx: 0, srcy: 0, destx: Random.Shared.Next(100), desty: Random.Shared.Next(100), (uint) img.width, (uint) img.height);

以上代码里面传入的 srcx 和 srcy 指的是从原图的哪里开始画起,而 destx 和 desty 则表示画到哪里

如此即可完成绘制图片逻辑

本文使用的 Program.cs 文件代码如下

using System.Runtime.Loader;
using static CPF.Linux.XLib;
using CPF.Linux;
using System.Runtime.InteropServices;XInitThreads();
var display = XOpenDisplay(IntPtr.Zero);
XError.Init();var screen = XDefaultScreen(display);var rootWindow = XDefaultRootWindow(display);XMatchVisualInfo(display, screen, 32, 4, out var info);
var visual = info.visual;var valueMask = SetWindowValuemask.BackPixmap| SetWindowValuemask.BackPixel| SetWindowValuemask.BorderPixel| SetWindowValuemask.BitGravity| SetWindowValuemask.WinGravity| SetWindowValuemask.BackingStore| SetWindowValuemask.ColorMap;
var attr = new XSetWindowAttributes
{backing_store = 1,bit_gravity = Gravity.NorthWestGravity,win_gravity = Gravity.NorthWestGravity,override_redirect = false,  // 参数:_overrideRedirectcolormap = XCreateColormap(display, rootWindow, visual, 0),
};var handle = XCreateWindow(display, rootWindow, 100, 100, 500, 500, 5,32,(int) CreateWindowArgs.InputOutput,visual,(nuint) valueMask, ref attr);var window = handle;XSelectInput(display, window, new IntPtr((int) XEventMask.ExposureMask));XMapWindow(display, window);
XFlush(display);var gc = XCreateGC(display, window, 0, 0);while (XNextEvent(display, out var xEvent) == default)
{if (xEvent.type == XEventName.Expose){XImage img = CreateImage();XPutImage(display, window, gc, ref img, 0, 0, Random.Shared.Next(100), Random.Shared.Next(100), (uint) img.width, (uint) img.height);}
}unsafe XImage CreateImage()
{var bitmapWidth = 50;var bitmapHeight = 50;const int bytePerPixelCount = 4; // RGBA 一共4个 byte 长度var bitPerByte = 8;var bitmapData = new byte[bitmapWidth * bitmapHeight * bytePerPixelCount];fixed (byte* p = bitmapData){int* pInt = (int*) p;var color = Random.Shared.Next();for (var i = 0; i < bitmapData.Length / (sizeof(int) / sizeof(byte)); i++){*(pInt + i) = color;}}GCHandle pinnedArray = GCHandle.Alloc(bitmapData, GCHandleType.Pinned);var img = new XImage();int bitsPerPixel = bytePerPixelCount * bitPerByte;img.width = bitmapWidth;img.height = bitmapHeight;img.format = 2; //ZPixmap;img.data = pinnedArray.AddrOfPinnedObject();img.byte_order = 0;// LSBFirst;img.bitmap_unit = bitsPerPixel;img.bitmap_bit_order = 0;// LSBFirst;img.bitmap_pad = bitsPerPixel;img.depth = bitsPerPixel;img.bytes_per_line = bitmapWidth * bytePerPixelCount;img.bits_per_pixel = bitsPerPixel;XInitImage(ref img);// 除非 XImage 不再使用了,否则此时释放,将会导致 GC 之后 data 指针对应的内存不是可用的// 调用 XPutImage 将访问不可用内存,导致段错误,闪退//pinnedArray.Free();return img;
}

本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin b2887797a8b04676407aa45618efbbaad732a6c2

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin b2887797a8b04676407aa45618efbbaad732a6c2

获取代码之后,进入 BujeeberehemnaNurgacolarje 文件夹,即可获取到源代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/795845.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dotnet C# 警惕可空结构体的方法内部赋值无效

本文将记录一个 C# dotnet 里的一个稍微隐藏的行为,那就是如果有一个结构体存在某个的方法,此方法的作用是修改结构里面的字段或属性的值,那此时将会在可空的结构体调用此方法时,发现没有真正修改到可空结构体局部变量本身其实这个问题非常好理解,只不过可能在编写代码的时…

南沙C++信奥老师解一本通题: 1206:放苹果

​【题目描述】把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。【输入】第一行是测试数据的数目t(0<=t<=20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。【输出】…

ServiceMesh 1:大火的云原生微服务网格,究竟好在哪里?

1 关于云原生 云原生计算基金会(Cloud Native Computing Foundation, CNCF)的官方描述是: 云原生是一类技术的统称,通过云原生技术,我们可以构建出更易于弹性扩展、极具分布式优势的应用程序。这些应用可以被运行在不同的环境当中,比如说 私有云、公有云、混合云、还有多…

数据库容灾等级

数据库容灾等级 在信息化时代,企业的数据安全和业务连续性变得至关重要,容灾备份作为确保数据不丢失和业务不中断的重要措施备受关注。 我们通常将容灾备份分为四个等级,从最基本的本地备份到复杂的异地多活系统,每个等级的特点和适用场景各不相同。下面我们就来详细了解一…

Visual Studio 查看项目的能力

在 Visual Studio 里面,可以在项目里面通过配置 DiagnoseCapabilities 查看项目的能力。什么是项目的能力?项目的能力就是对当前项目来说,可以具备 VS 支持的功能,项目功能是确定项目类型、平台和特性的推荐方法查看项目的能力的功能只适合于框架开发者使用,用于了解当前的…

vue3 setup语法糖 扩展

安装扩展 npm i vite-plugin-vue-setup-extend 修改配置文件接下啦就可以直接在标签中写name了

ISCC 2024 部分WP

练武题 WEB 还没想好名字的塔防游戏 题目中给了塔防游戏的github原项目地址。下载题目的网页源代码,和github项目对比,发现基本只加了world.js里的三个提示。Cats Craft Scarves Ivory Towers Twinkle Dragons Whisper Secrets提示不知道是什么意思。但是看首字母有点奇怪,另…

protobuf cmake Visual Studio 编译安装 (全命令行)

protobuf cmake Visual Studio 编译安装 (全命令行)protobuf cmake Visual Studio 编译安装 中间踩了挺多的坑的, 这篇文章记录一下. 重要前言: 所有在引用框中的命令都不要输入!!cmake --install . # 在引用框中的不要输入到命令行cmake --install . --config Debug # 命令…

陈彦吉的第一次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/zjlg/rjjc这个作业的目标 向教师和助教介绍自己,阐述自己期望的课程收获和扮演的课程实践角色姓名-学号 陈彦吉 2022329301139一、自我介绍 (一)基本信息 我叫陈彦吉,来自浙江台州,是2022级电气工程及其自动化(2)班…

(16)USB通信

USB协议讲解(大范围讲解) USB,英文全称 Universal Serial Bus(通用串行总线),是一种支持热插拔的高速串行传输总线(目前已发展至3.0) USB体系包括主机、设备以及物理连接三部分,其中: 主机是一个提供USB接口以及接口管理能力的硬件、软件及固件复合体,可以使PC,也可…

Redis 入门 - C#|.NET Core客户端库六种选择

介绍了6款.NET系Redis客户端库:ServiceStack.Redis、StackExchange.Redis、CSRedisCore、FreeRedis、NewLife.Redis、BeetleX.Redis,各具特色,如商业支持、高性能、高并发、低延迟等,适合不同场景和需求。经过前面的Redis基础学习,今天正式进入编码阶段了,进入编码阶段我…

[NLP] 知识抽取技术

1 概述:知识抽取 定义知识抽取通常指从非结构化文本中挖掘结构化信息。例如,含有丰富语义信息的标签和短语。 这在业界被广泛应用于内容理解和商品理解等场景,通过从用户生成的文本信息中提取有价值的标签,将其应用于内容或商品上知识抽取通常伴随着对所抽取标签或短语的分…