像素操作

优质
小牛编辑
133浏览
2023-12-01

在前面我们已经了解到图像的数据结构、栅格、坐标系和坐标转下标的方法,仅凭 LCUI 提供的这点图形 API 无法让我们轻松绘制复杂的图形,所以我们有必要学会如何操作像素数据,以便自己手动编码或借助其它图形库的能力来绘制图形。

注意,虽然像素数据的类型名是 LCUI_ARGB,但这些颜色通道的值在内存中存储的顺序是 BGRA。之所以采用这种顺序,是因为 Linux 中的帧缓存(FrameBuffer) 和 Windows 中的位图对象(HBITMAP) 都是这样。

图片灰度和反相颜色

在这个例子里,我们将图片分成四个部分,其中三个分别进行反色(inverted)、灰度(grayscale)、褐色(sepia)处理,然后将处理结果输出到图片文件中。invert() 函数将颜色的最大色值 255 减去像素的色值作为结果,grayscale() 函数将红绿蓝三色值的平均值作为结果,你也可以用加权平均,例如 x = 0.299r + 0.587g + 0.114b 这个公式,更多资料请参考维基百科的 Grayscalesepia() 函数与 grayscale() 函数采用了类似的算法。

像素操作的流程是相似的,先使用两层 for 循环在指定区域内定位要操作的像素点,然后获取像素数据进行处理,之后写入数据到图像中。为了减少代码量,我们将重复的代码定义成 PixelManipulationBegin 宏和 PixelManipulationEnd 宏,其中像素操作用到GraphGetPixel()GraphSetPixel() 是函数式宏,它封装了坐标转下标的计算代码,使得我们无需再编写冗长的代码,但需要注意的是,它会为每个像素点计算下标,而这计算中包含重复计算,所以效率比较低,如果你在实际项目中比较注重性能的话可以做一点优化,例如在 y 轴的循环里提前计算每行起点下标:row_start = y * graph.width + rect.x

#include <math.h>
#include <LCUI.h>
#include <LCUI/image.h>
​
#define PixelManipulationBegin                                   \
        int x, y;                                                \
        LCUI_Color pixel;                                        \
                                                                 \
        for (y = rect.y; y < rect.y + rect.height; ++y) {        \
                for (x = rect.x; x < rect.x + rect.width; ++x) { \
                        Graph_GetPixel(graph, x, y, pixel);
​
#define PixelManipulationEnd                \
        Graph_SetPixel(graph, x, y, pixel); \
        }                                   \
        }
​
void invert(LCUI_Graph *graph, LCUI_Rect rect)
{
        PixelManipulationBegin;
        pixel.red = (unsigned char)(255 - pixel.red);
        pixel.green = (unsigned char)(255 - pixel.green);
        pixel.blue = (unsigned char)(255 - pixel.blue);
        PixelManipulationEnd;
}
​
void grayscale(LCUI_Graph *graph, LCUI_Rect rect)
{
        unsigned char avg;
​
        PixelManipulationBegin;
        avg = (unsigned char)((pixel.red + pixel.green + pixel.blue) / 3);
        pixel.red = avg;
        pixel.green = avg;
        pixel.blue = avg;
        PixelManipulationEnd;
}
​
void sepia(LCUI_Graph *graph, LCUI_Rect rect)
{
        PixelManipulationBegin;
        pixel.red = (unsigned char)min(
            round(0.393 * pixel.red + 0.769 * pixel.green + 0.189 * pixel.blue),
            255);
        pixel.green = (unsigned char)min(
            round(0.349 * pixel.red + 0.686 * pixel.green + 0.168 * pixel.blue),
            255);
        pixel.blue = (unsigned char)min(
            round(0.272 * pixel.red + 0.534 * pixel.green + 0.131 * pixel.blue),
            255);
        PixelManipulationEnd;
}
​
int main(void)
{
        int i;
        LCUI_Graph graph = { 0 };
        LCUI_Rect rects[4];
​
        if (LCUI_ReadImageFile("dog.jpg", &graph) != 0) {
                return -1;
        }
        for (i = 0; i < 4; ++i) {
                rects[i].height = graph.height;
                rects[i].width = graph.width / 4;
                rects[i].x = i * rects[i].width;
                rects[i].y = 0;
        }
        sepia(&graph, rects[1]);
        grayscale(&graph, rects[2]);
        invert(&graph, rects[3]);
        LCUI_WritePNGFile("test_pixel_manipulation.png", &graph);
        return 0;
}
​

源图片:

dog.jpg

处理结果(原始,褐色,灰度,反色):

test_pixel_manipulation.png