简述:将带透明通道的PNG/BMP图片叠加到Framebuf上

PNG图片,维基百科简介:https://zh.wikipedia.org/wiki/PNG
现在带透明通道的最常见的PNG文件都是PNG 32格式,所以首先将PNG文件解析为RGBA-8-8-8-8即32位真彩像素(PNG 32)(A代表Alpha透明通道)

BMP 帧解析

BMP是最简单的图片文件格式,基本上就是为了存放在文件而勉强加了一些头尾而已。
看维基百科 https://en.wikipedia.org/wiki/BMP_file_format 的帧格式表,然后写结构体去序列化反序列化就能解出RGBA像素了。
以前用过c语言解析BMP,但是忘记写博客了,后面又写了一篇RUST解析BMP的分析文章:https://www.cnblogs.com/yucloud/p/rust-bmp_learn.html

PNG 帧解析

PNG文件编码比BMP复杂一点,但还是可以解析的。在no_std rust上我就偷懒使用了minipng库。
如果你需要c语言,可以自己写一个,参考标准文档RFC2083即可
需要Rust语言的解析器,可以参考:https://github.com/pommicket/minipng/blob/trunk/src/lib.rs

文件帧主要是单层TLV(Type-Length-Value)的方式。

RFC 2083 总结来说就是:

Header:文件格式头/也叫文件署名域,用于标识是否为PNG文件,避免了与其他格式的冲突和误判。
一个文件里有格式头,还有多个数据块。
每个数据块都由几个元素构成:长度,类型,数据体,CRC。这个基本所有的协议和文件都有类似设计。

PNG 定义了两种类型的数据块(chunk),一种是标准的关键数据块(critical chunk),另一种可选的辅助数据块(ancillary chunks)。我们只需要标准的关键数据块。

这里找到一篇表格化的帧解析文章:https://blog.csdn.net/qq_60131542/article/details/123450382

压缩算法:LZ77,哈夫曼huffman,Quantization

以下数据块I字开头是Image的意思

  • IHDR,这个是图片头块,包括了图片信息:宽高、位深、颜色类型、压缩算法、滤波器方法、隔行扫描方法。
  • PLTE 调色板区块(可选)
  • IDAT 图片数据块,像素RGBA就在这里。该区有多个块
  • IEND 图片结束块,也是特定的几个字节。
    还有其他的辅助区,但是一般我们用不到。总之TLV的方式可以让你通过 Type区分+Len偏移 只去拿需要的数据,而不用管其他区。

总之,RGBA从PNG文件解出来即可。

叠加算法

需要注意的是,透明通道A在屏幕上是不存在的,它唯一的作用是用于和背景运算叠加。也就是说,A通道是需要有背景色才能运算的。

这是将屏幕的RGB像素与现在PNG的RGBA像素合并/叠加的算法:
屏幕只能显示RGB,所有的A通道都会通过计算后叠加到RGB各分量上面

    fn blend_rgba_to_rgb(&self, background: &mut BltPixel) {
        // Alpha Blending
        // Result = Foreground×α + Background×(1−α)
        let alpha = self.0[3] as f32 / 255.0;
        let r = self.0[0] as f32;
        let g = self.0[1] as f32;
        let b = self.0[2] as f32;

        let blended_r = ((1.0 - alpha) * background.red as f32 + alpha * r) as u8;
        let blended_g = ((1.0 - alpha) * background.green as f32 + alpha * g) as u8;
        let blended_b = ((1.0 - alpha) * background.blue as f32 + alpha * b) as u8;

        background.red = blended_r;
        background.green = blended_g;
        background.blue = blended_b;
    }

以下是矩形叠加

///数学公式解法
//设:需要将png渲染到屏幕framebuf上矩形mfb上
//设:png的图片大小为(size_xp,size_yp)
//当前正在叠加渲染的坐标为 (x,y)
                for y in 0..size_yp {
                    for x in 0..size_xp {
                        vec_png[x + y * size_xp].blend_blt_pixel(&mut mfb[x + y * size_xp]);
                    }
                }

我们把从PNG文件里解出来的RGBA像素通过矩形叠加到屏幕framebuf的像素上,即可完成PNG的显示。
如果没办法获得矩形部分的mfb(mframebuf),也可以直接获取全屏framebuf再通过计算 需要渲染的起点坐标偏移(x0,y0),完成矩形部分叠加。

问题:这有什么用
答案:您看到的鼠标就是这样叠加上去的,另外还有显示器按键菜单等等OSD(on-screen display)功能,底层都是这个原理。

这篇文章是我为Slint-UI图像界面库贡献了一个UEFI的鼠标协议支持后写的一些记录,PR详见:https://github.com/slint-ui/slint/pull/5084/files

总结:最流行的文件/文件系统/分区格式/网络协议,基本上帧格式都是类似的 HDR(长度类型Checksum) Payload(多个数据块)END。结合一些魔法(TLV,算法指示,状态机指示)

posted @ 2024-04-21 19:25  蓝天上的云℡  阅读(27)  评论(0编辑  收藏  举报