【开源】嵌入式Linux(IMX6U)应用层综合项目(3)--相机

1.简介

此文章并不是教程,只能当作笔者的学习分享,只会做一些简单的介绍,其他的各位结合着代码和运行现象自己分析吧,相信通过函数名和注释,基本上是不难看懂代码的,其中涉及到的一些技术栈,也请各位学习到的时候多查阅资料。

本篇的内容为嵌入式Linux应用层的一个综合性比较强的项目,结尾会将源码放在网盘中开源出来,笔者能力有限,只是简单的把功能实现了,代码开源供大家一起交流学习,有什么好的建议,请各位一定不吝赐教!!!

1.1功能介绍

项目包括了四个app:

1.云平台的调试窗口,用于查看订阅主题所下发的数据,另一个为输入Json格式的数据来控制STM32单片机上的外设。

2.智能家居的界面,有4个图片按钮用于控制STM32板子上的LED灯、门(舵机)、蜂鸣器,量计分别为温度、湿度和亮度的值,同样是STM32获取发布到云平台的。

3.通过一个摄像头模块做的一个相机功能,可以拍照、录像,以及查看拍摄的照片,和播放录制视频的回放。

4.简易的音乐播放器:能够切换歌曲,以及暂停播放音乐。

1.2技术栈介绍

虽然项目简单,但是所涉及到的技术栈还是比较杂,我简单在此列出:

 1.LVGL库用于绘制UI。

2.MQTT协议,连接阿里云平台与STM32通讯。

3.alsa库用于音频处理。

4.LED、BEEP

5.V4L2 摄像头应用编程

1.3演示视频

【开源】Linux应用综合项目|云平台调试工具+智能家居+相机+音乐播放器_哔哩哔哩_bilibili

1.4硬件介绍

硬件使用的是正点原子的阿尔法开发板,芯片是IMX6U,类似开发板应该都可以运行。

2.软件设计

2.1.v4l2摄像头应用编程,实时监控和拍照功能实现。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include "ds_v4l2_camera.h"

#define FB_DEV "/dev/fb0"   // LCD设备节点
#define FRAMEBUFFER_COUNT 3 // 帧缓冲数量

/*** 摄像头像素格式及其描述信息 ***/
typedef struct camera_format
{
    unsigned char description[32]; // 字符串描述信息
    unsigned int pixelformat;      // 像素格式
} cam_fmt;

/*** 描述一个帧缓冲的信息 ***/
typedef struct cam_buf_info
{
    unsigned short *start; // 帧缓冲起始地址
    unsigned long length;  // 帧缓冲长度
} cam_buf_info;

static int width;                          // LCD宽度
static int height;                         // LCD高度
static unsigned short *screen_base = NULL; // LCD显存基地址
static int fb_fd = -1;                     // LCD设备文件描述符
static int v4l2_fd = -1;                   // 摄像头设备文件描述符
static cam_buf_info buf_infos[FRAMEBUFFER_COUNT];
static cam_fmt cam_fmts[10];
static int frm_width, frm_height; // 视频帧宽度和高度
static pthread_t g_ds_v4l2_camera_thread = NULL;

int capture_image = 0; // 全局变量,控制是否捕获图像
int total_photos = 0;
int current_photo_index = 0;

void *ds_v4l2_camera_thread(void *args);
void capture_single_image();

static int fb_dev_init(void)
{
    struct fb_var_screeninfo fb_var = {0};
    struct fb_fix_screeninfo fb_fix = {0};
    unsigned long screen_size;

    /* 打开framebuffer设备 */
    fb_fd = open(FB_DEV, O_RDWR);
    if (0 > fb_fd)
    {
        fprintf(stderr, "open error: %s: %s\n", FB_DEV, strerror(errno));
        return -1;
    }

    /* 获取framebuffer设备信息 */
    ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_fix);

    printf("fb_var.line_length = %d\n", fb_fix.line_length);
    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;
    printf("width = %d, height = %d\n", width, height);
    /* 内存映射 */
    screen_base = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
    printf("screen_base = %d\n", screen_base);
    if (MAP_FAILED == (void *)screen_base)
    {
        perror("mmap error");
        close(fb_fd);
        return -1;
    }

    /* LCD背景刷白 */
    // memset(screen_base, 0xFF, screen_size);
    memset(screen_base, 0x00, screen_size);
    return 0;
}

static int v4l2_dev_init(const char *device)
{
    struct v4l2_capability cap = {0};

    /* 打开摄像头 */
    v4l2_fd = open(device, O_RDWR);
    if (0 > v4l2_fd)
    {
        fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
        return -1;
    }

    /* 查询设备功能 */
    ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap);

    /* 判断是否是视频采集设备 */
    if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities))
    {
        fprintf(stderr, "Error: %s: No capture video device!\n", device);
        close(v4l2_fd);
        return -1;
    }

    return 0;
}

static void v4l2_enum_formats(void)
{
    struct v4l2_fmtdesc fmtdesc = {0};

    /* 枚举摄像头所支持的所有像素格式以及描述信息 */
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &fmtdesc))
    {

        // 将枚举出来的格式以及描述信息存放在数组中
        cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;
        strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);
        fmtdesc.index++;
    }
}

static void v4l2_print_formats(void)
{
    struct v4l2_frmsizeenum frmsize = {0};
    struct v4l2_frmivalenum frmival = {0};
    int i;

    frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for (i = 0; cam_fmts[i].pixelformat; i++)
    {

        printf("format<0x%x>, description<%s>\n", cam_fmts[i].pixelformat,
               cam_fmts[i].description);

        /* 枚举出摄像头所支持的所有视频采集分辨率 */
        frmsize.index = 0;
        frmsize.pixel_format = cam_fmts[i].pixelformat;
        frmival.pixel_format = cam_fmts[i].pixelformat;
        while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
        {

            printf("size<%d*%d> ",
                   frmsize.discrete.width,
                   frmsize.discrete.height);
            frmsize.index++;

            /* 获取摄像头视频采集帧率 */
            frmival.index = 0;
            frmival.width = frmsize.discrete.width;
            frmival.height = frmsize.discrete.height;
            while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival))
            {

                printf("<%dfps>", frmival.discrete.denominator /
                                      frmival.discrete.numerator);
                frmival.index++;
            }
            printf("\n");
        }
        printf("\n");
    }
}

static int v4l2_set_format(void)
{
    struct v4l2_format fmt = {0};
    struct v4l2_streamparm streamparm = {0};

    /* 设置帧格式 */
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // type类型
    // fmt.fmt.pix.width = width;              // 视频帧宽度
    // fmt.fmt.pix.height = height;            // 视频帧高度
    fmt.fmt.pix.width = IMAGE_WIDTH;               // 视频帧宽度
    fmt.fmt.pix.height = IMAGE_HEIGHT;             // 视频帧高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; // 像素格式
    if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt))
    {
        fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));
        return -1;
    }

    /*** 判断是否已经设置为我们要求的RGB565像素格式
    如果没有设置成功表示该设备不支持RGB565像素格式 */
    if (V4L2_PIX_FMT_RGB565 != fmt.fmt.pix.pixelformat)
    {
        fprintf(stderr, "Error: the device does not support RGB565 format!\n");
        return -1;
    }

    frm_width = fmt.fmt.pix.width;   // 获取实际的帧宽度
    frm_height = fmt.fmt.pix.height; // 获取实际的帧高度
    printf("视频帧大小<%d * %d>\n", frm_width, frm_height);

    /* 获取streamparm */
    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);

    /** 判断是否支持帧率设置 **/
    if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability)
    {
        streamparm.parm.capture.timeperframe.numerator = 1;
        streamparm.parm.capture.timeperframe.denominator = 30; // 30fps
        if (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm))
        {
            fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));
            return -1;
        }
    }

    return 0;
}

static int v4l2_init_buffer(void)
{
    struct v4l2_requestbuffers reqbuf = {0};
    struct v4l2_buffer buf = {0};

    /* 申请帧缓冲 */
    reqbuf.count = FRAMEBUFFER_COUNT; // 帧缓冲的数量
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    if (0 > ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqbuf))
    {
        fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));
        return -1;
    }

    /* 建立内存映射 */
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)
    {

        ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf);
        buf_infos[buf.index].length = buf.length;
        buf_infos[buf.index].start = mmap(NULL, buf.length,
                                          PROT_READ | PROT_WRITE, MAP_SHARED,
                                          v4l2_fd, buf.m.offset);
        if (MAP_FAILED == buf_infos[buf.index].start)
        {
            perror("mmap error");
            return -1;
        }
    }

    /* 入队 */
    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)
    {

        if (0 > ioctl(v4l2_fd, VIDIOC_QBUF, &buf))
        {
            fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));
            return -1;
        }
    }

    return 0;
}

static int v4l2_stream_on(void)
{
    /* 打开摄像头、摄像头开始采集数据 */
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (0 > ioctl(v4l2_fd, VIDIOC_STREAMON, &type))
    {
        fprintf(stderr, "ioctl error: VIDIOC_STREAMON: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

void close_v4l2_camera(void)
{
    // 停止线程
    if (g_ds_v4l2_camera_thread != NULL)
    {
        pthread_cancel(g_ds_v4l2_camera_thread);
        pthread_join(g_ds_v4l2_camera_thread, NULL);
        g_ds_v4l2_camera_thread = NULL;
    }

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (0 > ioctl(v4l2_fd, VIDIOC_STREAMOFF, &type))
    {
        fprintf(stderr, "ioctl error: VIDIOC_STREAMOFF: %s\n", strerror(errno));
    }

    // 取消映射缓冲区
    for (int i = 0; i < FRAMEBUFFER_COUNT; i++)
    {
        if (buf_infos[i].start != NULL && buf_infos[i].length > 0)
        {
            if (munmap(buf_infos[i].start, buf_infos[i].length) < 0)
            {
                fprintf(stderr, "munmap error: %s\n", strerror(errno));
            }
            buf_infos[i].start = NULL;
            buf_infos[i].length = 0;
        }
    }
}

int ds_v4l2_camera_init(void)
{
    static bool v4l2_dev_flag = 0;
    /* 初始化LCD */
    if (fb_dev_init())
        exit(EXIT_FAILURE);

    if (v4l2_dev_flag == 0)
    {
        printf("打开摄像头");
        v4l2_dev_flag = 1;
        /* 初始化摄像头 */
        if (v4l2_dev_init("/dev/video1"))
            exit(EXIT_FAILURE);
    }
    else
    {
        printf("摄像头已打开,不可重复打开\n");
    }

    /* 枚举所有格式并打印摄像头支持的分辨率及帧率 */
    v4l2_enum_formats();
    v4l2_print_formats();

    /* 设置格式 */
    if (v4l2_set_format())
        exit(EXIT_FAILURE);

    /* 初始化帧缓冲:申请、内存映射、入队 */
    if (v4l2_init_buffer())
        exit(EXIT_FAILURE);

    /* 开启视频采集 */
    if (v4l2_stream_on())
        exit(EXIT_FAILURE);

    int res;
    res = pthread_create(&g_ds_v4l2_camera_thread, NULL, ds_v4l2_camera_thread, NULL);
    if (res != 0)
    {
        printf("pthread_create ds_v4l2_camera_thread failed: %d\n", res);
        return -1;
    }
    printf("ds_v4l2_camera_thread created successfully\n");
}

static void save_image(unsigned short *data, int width, int height, FILE *filename)
{
    // FILE *file = fopen(PHOTO_PATH, "wb");
    FILE *file = fopen(filename, "wb");
    if (!file)
    {
        perror("Error opening file for writing");
        return;
    }

    size_t size = width * height * 2; // RGB565: 2 bytes per pixel
    size_t written = fwrite(data, 1, size, file);
    if (written != size)
    {
        perror("Error writing image data to file");
    }

    fclose(file);

    printf("First few bytes of image data: %02x %02x %02x\n",
           ((unsigned char *)data)[0],
           ((unsigned char *)data)[1],
           ((unsigned char *)data)[2]);

    printf("Image saved as %s\n", filename);
}

static void save_image_Multiple(const unsigned short *data, int width, int height, const char *filename)
{
    FILE *file = fopen(filename, "wb");
    if (!file)
    {
        perror("Failed to open file for saving image");
        return;
    }

    size_t size = width * height * 2; // RGB565: 2 bytes per pixel
    fwrite(data, 1, size, file);
    fclose(file);
    printf("Image saved successfully as %s\n", filename);
}

void capture_multiple_images(int num_images)
{
    char filename[256];

    for (int i = 0; i < num_images; i++)
    {
        struct v4l2_buffer buf = {0};
        ioctl(v4l2_fd, VIDIOC_DQBUF, &buf); // 出队

        snprintf(filename, sizeof(filename), "image_%d.rgb", i + 1);

        save_image(buf_infos[buf.index].start, 640, 480, filename);

        ioctl(v4l2_fd, VIDIOC_QBUF, &buf); // 入队
    }
}

static void v4l2_read_data(void)
{
    struct v4l2_buffer buf = {0};
    unsigned short *base;
    unsigned short *start;
    int min_w, min_h;
    int j;
    const int x = 80, y = 0;

    if (width > frm_width)
        min_w = frm_width;
    else
        min_w = width;
    if (height > frm_height)
        min_h = frm_height;
    else
        min_h = height;

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)
    {
        ioctl(v4l2_fd, VIDIOC_DQBUF, &buf); // 出队

#if (IMAGE_WIDTH == 800 && IMAGE_HEIGHT == 480)
        {
            printf("Base pointer address: %p\n", screen_base);
            for (j = 0, base = screen_base, start = buf_infos[buf.index].start;
                 j < min_h; j++)
            {
                memcpy(base, start, min_w * 2); // RGB565 一个像素占2个字节
                base += width;                  // LCD显示指向下一行
                start += frm_width;             // 指向下一行数据
            }
        }
#else
        {
            base = screen_base + y * width + x;
            // printf("Base pointer address: %p\n", base);
            for (j = 0, base = screen_base + y * width + x, start = buf_infos[buf.index].start; j < min_h; j++)
            {
                memcpy(base, start, min_w * 2); // RGB565 一个像素占2个字节
                base += width;                  // LCD显示指向下一行
                start += frm_width;             // 指向下一行数据
            }
        }
#endif
        if (capture_image)
        {
            capture_image = 0; // 只捕获一次
            clear_area(80, 0, 640, 480, lv_color_hex(MY_UI_COLOR_BLACK));
            usleep(100 * 1000);

            static int image_count = 0;
            char photo_path[256];
            snprintf(photo_path, sizeof(photo_path), "/home/root/img_rgb/photo_%d.rgb", image_count++);
            save_image(buf_infos[buf.index].start, IMAGE_WIDTH, IMAGE_HEIGHT, photo_path);
            total_photos = image_count;
            printf("total_photos = %d\n", total_photos);
            // current_photo_index = image_count;
            //  save_image(buf_infos[buf.index].start, IMAGE_WIDTH, IMAGE_HEIGHT);
        }
        // 数据处理完之后、再入队、往复
        ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
    }
}

void display_photo(const char *photo_path, int pho_width, int pho_height)
{
    FILE *file = fopen(photo_path, "rb");
    if (!file)
    {
        perror("Failed to open photo file");
        return;
    }

    size_t pho_size = pho_width * pho_height * 2; // RGB565: 2 bytes per pixel
    size_t screen_size = 800 * 480;
    void *image_data = malloc(pho_size);
    if (!image_data)
    {
        perror("Failed to allocate memory for photo");
        fclose(file);
        return;
    }

    size_t read_bytes = fread(image_data, 1, pho_size, file);
    if (read_bytes != pho_size)
    {
        perror("Failed to read photo data");
        printf("size = %d\n", pho_size);
        printf("read_bytes = %d\n", read_bytes);
        free(image_data);
        fclose(file);
        return;
    }
    fclose(file);

#if (IMAGE_WIDTH == 800 && IMAGE_HEIGHT == 480)
    memcpy(screen_base, image_data, pho_size);
#else
    // Copy image data to framebuffer
    unsigned short *base = (unsigned short *)(screen_base + 0 * pho_width + 80);
    unsigned short *start = (unsigned short *)image_data;
    printf("Photo base address: %p\n", base);
    printf("Image data address: %p\n", start);

    for (int y = 0; y < pho_height; y++)
    {
        memcpy(base, start, pho_width * 2); // RGB565 每像素占 2 个字节
        base += 800;                        // LCD 显示指向下一行
        start += pho_width;                 // 图片数据指向下一行
    }
#endif
    free(image_data);
    printf("Photo displayed successfully\n");
}

void *ds_v4l2_camera_thread(void *args)
{
    while (1)
    {
        v4l2_read_data();
        usleep(10 * 1000);
    }
    return NULL;
}

2.2.按键和相册UI

#include "ui_app_camera.h"

LV_IMG_DECLARE(img_back_64);
LV_IMG_DECLARE(img_takephoto_64);
LV_IMG_DECLARE(img_takevedio_64);
LV_IMG_DECLARE(img_image_64);
LV_IMG_DECLARE(img_image_prev_64);
LV_IMG_DECLARE(img_image_next_64);

lv_obj_t *back_btn;
lv_obj_t *photo_vedio_btn;
lv_obj_t *image_btn;
extern int app_index;
extern int current_photo_index;
extern int total_photos;

void show_current_photo(void)
{
    char photo_path[256];
    printf("current_photo_index = %d\n", current_photo_index);
    snprintf(photo_path, sizeof(photo_path), "/home/root/img_rgb/photo_%d.rgb", current_photo_index);
    display_photo(photo_path, 640, 480);
}

static void back_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("back_event_cb clicked!\n");
        close_v4l2_camera();
        app_index = 0;
        ui_page_main();
    }
}

extern int capture_image;
static void photo_vedio_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);

    if (code == LV_EVENT_SHORT_CLICKED)
    {
        printf("photo_vedio_event_cb PRESSED!\n");
        lv_img_set_src(photo_vedio_btn, &img_takephoto_64);
        capture_image = 1;
    }
}

bool is_image = 0;
static void check_image_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("check_image_event_cb clicked!\n");
        is_image = !is_image;
        if (is_image)
        {
            ui_check_image();
            current_photo_index = total_photos - 1;
            printf("current_photo_index = %d\n", current_photo_index);
            show_current_photo();
        }
        else
        {
            ui_page_main();
        }
    }
}

static void prev_image_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("prev_image_event_cb clicked!\n");
        if (current_photo_index > 0)
        {
            current_photo_index--;
            printf("current_photo_index = %d\n", current_photo_index);
            show_current_photo();
        }
    }
}

static void next_image_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("next_image_event_cb clicked!\n");
        if (current_photo_index < total_photos - 1)
        {
            current_photo_index++;
            printf("current_photo_index = %d\n", current_photo_index);
            show_current_photo();
        }
    }
}

void ui_app_camera(void)
{
    clear_area(0, 0, 800, 480, lv_color_hex(MY_UI_COLOR_BLACK));
    ds_v4l2_camera_init();

    back_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(back_btn, 64, 64);
    lv_obj_add_event_cb(back_btn, back_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(back_btn, &img_back_64);
    lv_obj_align(back_btn, LV_ALIGN_TOP_LEFT, 10, 10);

    photo_vedio_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(photo_vedio_btn, 64, 64);
    lv_obj_add_event_cb(photo_vedio_btn, photo_vedio_event_cb, LV_EVENT_ALL, NULL);
    lv_img_set_src(photo_vedio_btn, &img_takephoto_64);
    lv_obj_align(photo_vedio_btn, LV_ALIGN_TOP_LEFT, 730, 205);

    image_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(image_btn, 64, 64);
    lv_obj_add_event_cb(image_btn, check_image_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(image_btn, &img_image_64);
    lv_obj_align(image_btn, LV_ALIGN_TOP_LEFT, 730, 380);
}

void ui_check_image(void)
{
    close_v4l2_camera();

    clear_area(0, 0, 800, 480, lv_color_hex(MY_UI_COLOR_BLACK));
    lv_refr_now(NULL); // 立即刷新屏幕
    sleep(1);
    // show_current_photo();
    //  display_photo(PHOTO_PATH, IMAGE_WIDTH, IMAGE_HEIGHT);

    image_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(image_btn, 64, 64);
    lv_obj_add_event_cb(image_btn, check_image_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(image_btn, &img_image_64);
    lv_obj_align(image_btn, LV_ALIGN_TOP_LEFT, 730, 380);

    lv_obj_t *image_prev_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(image_prev_btn, 64, 64);
    lv_obj_add_event_cb(image_prev_btn, prev_image_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(image_prev_btn, &img_image_prev_64);
    lv_obj_align(image_prev_btn, LV_ALIGN_TOP_LEFT, 10, 205);

    lv_obj_t *image_next_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(image_next_btn, 64, 64);
    lv_obj_add_event_cb(image_next_btn, next_image_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(image_next_btn, &img_image_next_64);
    lv_obj_align(image_next_btn, LV_ALIGN_TOP_LEFT, 725, 205);
}

3.结尾(附网盘链接)

通过百度网盘分享的文件:Linux_4_APP_Demo.zip

链接:百度网盘 请输入提取码

提取码:97km

--来自百度网盘超级会员V5的分享

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值