当前位置: 首页 > article >正文

LVGL图像导入和解码

LVGL版本:8.1

概述

       在LVGL中,可以导入多种不同类型的图像:

  • 经转换器生成的C语言数组,适用于页面中不常改变的固定图像。
  • 存储系统中的外部图像,比较灵活,可以通过插卡或从网络中获取,但需要配置相应的驱动和解码器,解码过程也需要占用时间和内存。
  • SYMBOL类型的文本,和label组件类似。

       其中第三种比较容易实现,第一种仅需经过官方工具生成二进制数据,再在代码中引用即可。本文重点说明第二种情况即外部图像的使用,并以PNG类型图片为例介绍LVGL图像的解码和显示。

原理框图

       图像的解析和使用可以分以下几层:

        图中的虚线框可有可无,有的仅代表多一层函数,为了更好的理解,在图上加入这些虚线框。

       对于一个通过lv_img_create()函数创建的lv_img_t图像,可以通过设置图像源进行图像导入,LVGL会先查看缓存,如果有命中则直接使用,否则进入解码环节。对于内置图像如SYMBOL和VARIABLE(即C数组)类型,可以直接使用内置的解码器,在编译时会直接合并入程序。对于外部图像格式如jpg和png,则需要添加额外的解码器,并且,由于这些外部图像通常是以文件的形式存放在文件系统中,还需要提供基本的文件操作来读取文件,如Linux可以简单使用POSIX标准的系统调用函数(open、read、write等),事实上,如果没有提供文件系统,用户也可以自定义图像的打开和读取方式,只要符合接口标准即可。

    开启配置

           以PNG类型的图像为例,需要在lv_conf.h配置文件中,打开LV_USE_PNG开关,同时要开启文件系统(根据实际所在系统进行选择),比如在这里我选择的是LV_USE_FS_POSIX,即使用POSIX标准的文件操作函数(read、write等),也是Linux的原生操作。注意这里要求填入一个前缀来区分不同系统,通常使用类似“盘符”的字母(如这里使用了'S'),因为LVGL底层打开源路径时也是比对的路径首字母,配对成功后才会使用对应的驱动来打开。

    //lv_conf.h
    /* 文件系统,如开启,请设置相应的驱动字母(或前缀) */
    #define LV_USE_FS_STDIO '\0'        /*Uses fopen, fread, etc*/
    //#define LV_FS_STDIO_PATH "/home/john/"    /*Set the working directory. If commented it will be "./" */#define LV_USE_FS_POSIX 'S'        /*Uses open, read, etc*/
    //#define LV_FS_POSIX_PATH "/home/john/"    /*Set the working directory. If commented it will be "./" */#define LV_USE_FS_WIN32 '\0'        /*Uses CreateFile, ReadFile, etc*/
    //#define LV_FS_WIN32_PATH "C:\\Users\\john\\"    /*Set the working directory. If commented it will be ".\\" */#define LV_USE_FS_FATFS '\0'        /*Uses f_open, f_read, etc*//*PNG decoder library*/
    #define LV_USE_PNG 1

           此外,还可以增加系统的图像缓存数量LV_IMG_CACHE_DEF_SIZE,来减少不必要的解码,提高渲染效率。

    //lv_conf.h
    #define LV_IMG_CACHE_DEF_SIZE 1   //图像缓存数量,只有使用外部解码器的图像,该配置才有意义

     

    图像解码

           通过lv_img_set_src()函数设置图像源来开启LVGL解码和显示的过程,其中图像源src可以是以下类型:

    • 图像路径,通常以一个盘符为首(如'S'或'S:'),后面跟绝对路径,比如是'S/img/abc.png',注意图像要预先导入到设备对应路径上才能读取
    • LVGL规定的SYMBOL类型,也可以通过官方工具生成自定义SYMBOL;
    • C语言数组,包含图像的位置和色彩信息,可以通过官方工具将其它类型的图像转换为LVGL可识别的数据文件,并在使用处进行声明和导入。

           在这里,该函数仅用于设置图像源,并获取图像基本信息,还没有到解码部分。

    //lv_img.c
    void lv_img_set_src(lv_obj_t * obj, const void * src)
    {LV_ASSERT_OBJ(obj, MY_CLASS);lv_obj_invalidate(obj);lv_img_src_t src_type = lv_img_src_get_type(src);  //获取来源类型lv_img_t * img = (lv_img_t *)obj;/*If the new source type is unknown free the memories of the old source*/if(src_type == LV_IMG_SRC_UNKNOWN) {LV_LOG_WARN("lv_img_set_src: unknown image type");if(img->src_type == LV_IMG_SRC_SYMBOL || img->src_type == LV_IMG_SRC_FILE) {lv_mem_free((void *)img->src);}img->src      = NULL;img->src_type = LV_IMG_SRC_UNKNOWN;return;}lv_img_header_t header;lv_img_decoder_get_info(src, &header);  //通过图片来源获取对应的解码器/*Save the source*/if(src_type == LV_IMG_SRC_VARIABLE) {/*If memory was allocated because of the previous `src_type` then free it*/if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {lv_mem_free((void *)img->src);}img->src = src;}else if(src_type == LV_IMG_SRC_FILE || src_type == LV_IMG_SRC_SYMBOL) {/*If the new and the old src are the same then it was only a refresh.*/if(img->src != src) {const void * old_src = NULL;/*If memory was allocated because of the previous `src_type` then save its pointer and free after allocation.*It's important to allocate first to be sure the new data will be on a new address.*Else `img_cache` wouldn't see the change in source.*/if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {old_src = img->src;}char * new_str = lv_mem_alloc(strlen(src) + 1);LV_ASSERT_MALLOC(new_str);if(new_str == NULL) return;strcpy(new_str, src);img->src = new_str;if(old_src) lv_mem_free((void *)old_src);}}if(src_type == LV_IMG_SRC_SYMBOL) {/*`lv_img_dsc_get_info` couldn't set the with and height of a font so set it here*/const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);lv_point_t size;lv_txt_get_size(&size, src, font, letter_space, line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);header.w = size.x;header.h = size.y;}img->src_type = src_type;   //图像类型(变量/符号/文件路径)img->w        = header.w;   //图像宽度img->h        = header.h;   //图像高度img->cf       = header.cf;   //图像色彩信息img->pivot.x = header.w / 2;img->pivot.y = header.h / 2;lv_obj_refresh_self_size(obj);/*Provide enough room for the rotated corners*/if(img->angle || img->zoom != LV_IMG_ZOOM_NONE) lv_obj_refresh_ext_draw_size(obj);lv_obj_invalidate(obj);   //标记无效区域,下次更新才会正式解码图片
    }

           其中lv_img_decoder_get_info()用于获取图像源的基本信息,该函数会首先遍历解码器链表,匹配合适的解码器,然后将图像的宽、高、色彩信息记录下来。

    //lv_img_decoder.c
    lv_res_t lv_img_decoder_get_info(const void * src, lv_img_header_t * header)
    {lv_memset_00(header, sizeof(lv_img_header_t));if(src == NULL) return LV_RES_INV;lv_img_src_t src_type = lv_img_src_get_type(src);if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;if(img_dsc->data == NULL) return LV_RES_INV;}lv_res_t res = LV_RES_INV;lv_img_decoder_t * d;_LV_LL_READ(&LV_GC_ROOT(_lv_img_decoder_ll), d) {  //遍历解码器链表,为图像源寻找合适的解码器if(d->info_cb) {res = d->info_cb(d, src, header);  //调用info回调,获取图像信息,header用于保存这些信息if(res == LV_RES_OK) break;   //返回OK,说明解码器配对成功,退出并返回}}return res;
    }

           如果定义了LV_USE_PNG,则默认注册LVGL自带的PNG解码器,其中包含获取基本信息的回调函数decoder_info()

    //lv_png.c
    static lv_res_t decoder_info(struct _lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header)
    {(void) decoder; /*Unused*/lv_img_src_t src_type = lv_img_src_get_type(src);     /* 获取来源类型 *//* 检查是否是文件路径类型 */if(src_type == LV_IMG_SRC_FILE) {const char * fn = src;if(!strcmp(&fn[strlen(fn) - 3], "png")) {     /* 检查后缀是否为png *//* Read the width and height from the file. They have a constant location:* [16..23]: width* [24..27]: height*/uint32_t size[2];lv_fs_file_t f;lv_fs_res_t res = lv_fs_open(&f, fn, LV_FS_MODE_RD);  //使用对应的驱动打开文件if(res != LV_FS_RES_OK) return LV_RES_INV;lv_fs_seek(&f, 16, LV_FS_SEEK_SET);uint32_t rn;lv_fs_read(&f, &size, 8, &rn);if(rn != 8) return LV_RES_INV;lv_fs_close(&f);/*Save the data in the header*/header->always_zero = 0;header->cf = LV_IMG_CF_RAW_ALPHA;/*The width and height are stored in Big endian format so convert them to little endian*/header->w = (lv_coord_t) ((size[0] & 0xff000000) >> 24) +  ((size[0] & 0x00ff0000) >> 8);header->h = (lv_coord_t) ((size[1] & 0xff000000) >> 24) +  ((size[1] & 0x00ff0000) >> 8);return LV_RES_OK;}}/*If it's a PNG file in a  C array...*/else if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;header->always_zero = 0;header->cf = img_dsc->header.cf;       /*Save the color format*/header->w = img_dsc->header.w;         /*Save the color width*/header->h = img_dsc->header.h;         /*Save the color height*/return LV_RES_OK;}return LV_RES_INV;         /*If didn't succeeded earlier then it's an error*/
    }

           其中lv_fs_open()函数是寻找合适的驱动来打开这个文件,我们在前面定义了LV_USE_FS_POSIX,LVGL在初始化阶段就会自动注册POSIX标准驱动,使用open、read、write等函数打开对应文件。

    //lv_fs_posix.c
    void lv_fs_posix_init(void)
    {static lv_fs_drv_t fs_drv; /* 驱动描述子 */lv_fs_drv_init(&fs_drv);fs_drv.letter = LV_USE_FS_POSIX;  //文件系统前缀,可以简单设置为'S'fs_drv.open_cb = fs_open;fs_drv.close_cb = fs_close;fs_drv.read_cb = fs_read;fs_drv.write_cb = fs_write;fs_drv.seek_cb = fs_seek;fs_drv.tell_cb = fs_tell;fs_drv.dir_close_cb = fs_dir_close;fs_drv.dir_open_cb = fs_dir_open;fs_drv.dir_read_cb = fs_dir_read;lv_fs_drv_register(&fs_drv);
    }

           到这里为止仅仅只是把图像源记录下来,直到下次更新周期的绘制阶段,才会正式解析图像。下面直接跳转到图像的绘制函数lv_draw_img_core(),这里仅截取解码图像的代码片段。

    //lv_draw_img.c
    LV_ATTRIBUTE_FAST_MEM static lv_res_t lv_img_draw_core(const lv_area_t * coords, const lv_area_t * clip_area,const void * src,const lv_draw_img_dsc_t * draw_dsc)
    {if(draw_dsc->opa <= LV_OPA_MIN) return LV_RES_OK;_lv_img_cache_entry_t * cdsc = _lv_img_cache_open(src, draw_dsc->recolor, draw_dsc->frame_id);if(cdsc == NULL) return LV_RES_INV;/*......*/
    }

           可以看到,LVGL在绘制图像前先询问缓存,即_lv_img_cache_open()函数,该函数会先遍历缓存链表,查找是否存在匹配的缓存,如匹配直接返回该缓存,否则从链表中找一个存活时间最短的缓存,用新的图像替换掉该旧缓存。

    //lv_img_cache.c
    _lv_img_cache_entry_t * _lv_img_cache_open(const void * src, lv_color_t color, int32_t frame_id)
    {_lv_img_cache_entry_t * cached_src = NULL;#if LV_IMG_CACHE_DEF_SIZEif(entry_cnt == 0) {LV_LOG_WARN("lv_img_cache_open: the cache size is 0");return NULL;}_lv_img_cache_entry_t * cache = LV_GC_ROOT(_lv_img_cache_array);/*Decrement all lifes. Make the entries older*/uint16_t i;for(i = 0; i < entry_cnt; i++) {if(cache[i].life > INT32_MIN + LV_IMG_CACHE_AGING) {cache[i].life -= LV_IMG_CACHE_AGING;}}for(i = 0; i < entry_cnt; i++) {if(color.full == cache[i].dec_dsc.color.full &&frame_id == cache[i].dec_dsc.frame_id &&lv_img_cache_match(src, cache[i].dec_dsc.src)) {  //是否命中缓存/* 打开一个图像也会增加图像的存活时间,因此在这里对缓存增加一个time_to_open的时间 */cached_src = &cache[i];cached_src->life += cached_src->dec_dsc.time_to_open * LV_IMG_CACHE_LIFE_GAIN;if(cached_src->life > LV_IMG_CACHE_LIFE_LIMIT) cached_src->life = LV_IMG_CACHE_LIFE_LIMIT;LV_LOG_TRACE("image source found in the cache");break;}}/* 命中缓存后直接返回该缓存 */if(cached_src) return cached_src;/* 如果没有命中,则在缓存中寻找一个合适的位置存放新图像 */cached_src = &cache[0];for(i = 1; i < entry_cnt; i++) {if(cache[i].life < cached_src->life) {   //寻找生存时间最短的图像cached_src = &cache[i];}}/* 找到被替代的缓存后,要先关闭它的解码器 */if(cached_src->dec_dsc.src) {lv_img_decoder_close(&cached_src->dec_dsc);LV_LOG_INFO("image draw: cache miss, close and reuse an entry");}else {LV_LOG_INFO("image draw: cache miss, cached to an empty entry");}
    #elsecached_src = &LV_GC_ROOT(_lv_img_cache_single);
    #endif/* 打开一个新的解码器 */uint32_t t_start  = lv_tick_get();lv_res_t open_res = lv_img_decoder_open(&cached_src->dec_dsc, src, color, frame_id);if(open_res == LV_RES_INV) {LV_LOG_WARN("Image draw cannot open the image resource");lv_memset_00(cached_src, sizeof(_lv_img_cache_entry_t));cached_src->life = INT32_MIN; /*Make the empty entry very "weak" to force its us*/return NULL;}cached_src->life = 0;   //记录缓存后,设置存活时间从0开始/* 记录解码它的时间 */if(cached_src->dec_dsc.time_to_open == 0) {cached_src->dec_dsc.time_to_open = lv_tick_elaps(t_start);}if(cached_src->dec_dsc.time_to_open == 0) cached_src->dec_dsc.time_to_open = 1;return cached_src;
    }

           当没有命中缓存时,会调用lv_img_decoder_open()函数尝试使用解码器打开该图像,打开成功后,会将图像数据提取出来。

    //lv_img_decoder.c
    lv_res_t lv_img_decoder_open(lv_img_decoder_dsc_t * dsc, const void * src, lv_color_t color, int32_t frame_id)
    {lv_memset_00(dsc, sizeof(lv_img_decoder_dsc_t));if(src == NULL) return LV_RES_INV;lv_img_src_t src_type = lv_img_src_get_type(src);if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;if(img_dsc->data == NULL) return LV_RES_INV;}dsc->color    = color;dsc->src_type = src_type;dsc->frame_id = frame_id;if(dsc->src_type == LV_IMG_SRC_FILE) {size_t fnlen = strlen(src);dsc->src = lv_mem_alloc(fnlen + 1);LV_ASSERT_MALLOC(dsc->src);if(dsc->src == NULL) {LV_LOG_WARN("lv_img_decoder_open: out of memory");return LV_RES_INV;}strcpy((char *)dsc->src, src);}else {dsc->src = src;}lv_res_t res = LV_RES_INV;lv_img_decoder_t * decoder;_LV_LL_READ(&LV_GC_ROOT(_lv_img_decoder_ll), decoder) {   //遍历解码器链表/* info和open函数是必要的 */if(decoder->info_cb == NULL || decoder->open_cb == NULL) continue;res = decoder->info_cb(decoder, src, &dsc->header);if(res != LV_RES_OK) continue;  //返回OK,说明命中解码器dsc->decoder = decoder;res = decoder->open_cb(decoder, dsc);  //使用解码器打开图像文件/* 如果返回OK,表示成功打开图像文件,则返回 */if(res == LV_RES_OK) return res;/* 准备遍历下一个解码器节点 */lv_memset_00(&dsc->header, sizeof(lv_img_header_t));dsc->error_msg = NULL;dsc->img_data  = NULL;dsc->user_data = NULL;dsc->time_to_open = 0;}if(dsc->src_type == LV_IMG_SRC_FILE)lv_mem_free((void *)dsc->src);return res;
    }

           接下来就是使用对应解码器的open函数来获取图像,这里就不进一步说明了。可以参考官方对于不同格式图像的解码器例程代码。

    相关文章:

    LVGL图像导入和解码

    LVGL版本&#xff1a;8.1 概述 在LVGL中&#xff0c;可以导入多种不同类型的图像&#xff1a; 经转换器生成的C语言数组&#xff0c;适用于页面中不常改变的固定图像。存储系统中的外部图像&#xff0c;比较灵活&#xff0c;可以通过插卡或从网络中获取&#xff0c;但需要配置…...

    Vite Proxy配置详解:从入门到实战应用

    Vite Proxy配置详解&#xff1a;从入门到实战应用 一、什么是Proxy代理&#xff1f; Proxy&#xff08;代理&#xff09;是开发中常用的解决跨域问题的方案。Vite内置了基于http-proxy的代理功能&#xff0c;可以轻松配置API请求转发。 二、基础配置 在vite.config.js中配置…...

    oracle goldengate非并行进程转换为并行进程

    oracle goldengate非并行进程转换为并行进程 在上一期的文章中写道了直接创建并行进程的方式对大事务进行分解&#xff0c;这对于新建立同步进程的时候提前规划是很有帮助的&#xff0c;但是如果对已经进行了同步的进程重新建立需要耗时比较长&#xff0c;Oracle提供了非并行进…...

    VBA将PDF文档内容逐行写入Excel

    VBA是无法直接读取PDF文档的&#xff0c;但结合上期我给大家介绍了PDF转换工具xpdf-tools-4.05&#xff0c;先利用它将PDF文档转换为TXT文档&#xff0c;然后再将TXT的内容写入Excel&#xff0c;这样就间接实现了将PDF文档的内容导入Excel的操作。下面的代码将向大家演示如何实…...

    project从入门到精通(五)

    目录 创建资源的基本信息 在project中创建资源工作表 ​编辑信息详解 最大单位 标准费率与加班费率 每次使用成本 成本累算 基准日历 三类资源工作表的总结——不同的资源必须要设置的属性 除了资源名称是必须设置的之外&#xff0c;剩余的资源的可设置选项如下图所…...

    第3.2.3节 Android动态调用链路的获取

    3.2.3 Android App动态调用链路 在Android应用中&#xff0c;动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系&#xff0c;特别是在应用的复杂逻辑中&#xff0c;理解这些调用链路对于调试和性能优化非常重要。 1&#xff0c;动态调用链路获…...

    亿级流量系统架构设计与实战(六)

    微服务架构与网络调用 当某个业务从单体服务架构转变为微服务架构后,多个服务之间会通过网络调用形式形成错综复杂的依赖关系。 在微服务架构中 , 一个微服务正常工作依赖它与其他微服务之间的多级网络调用。 网络是脆弱的 , RPC 请求有较大的概率会遇到超时 、 抖动 、 断…...

    浅聊find_package命令的搜索模式(Search Modes)

    背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的&#xff1f; 根据官方文档&#xff0c;整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种&#xff1a;模块模式(Module mode)和配置模式(Conf…...

    开发搭载OneNet平台的物联网数据收发APP的设计与实现

    一、开发环境与工具准备 工具安装 下载HBuilderX开发版(推荐使用开发版以避免插件兼容性问题)安装Node.js和npm(用于依赖管理及打包)配置Android Studio(本地打包需集成离线SDK)项目初始化 创建uni-app项目,选择“默认模板”或“空白模板”安装必要的UI库(如uView或Van…...

    【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B

    【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B 本地环境说明禁用开源驱动nouveau安装nvidia-smi安装Git环境安装Anaconda(conda)环境下载DeepSeek-R1-Distill-Qwen-7B模型安装LLaMA-Factory下载LLaMA-Factory安装LLaMA-Factory依赖修改环境变量安装deepspeedA…...

    sh脚本多卡顺序执行训练文件

    常规的单机多卡训练脚本一般为 python -m torch.distributed.run --nproc_per_node 2 train.py 上述脚本采用 2 张显卡训练 采用sh脚本&#xff0c;单次顺序执行多个多卡训练文件 例如 train1.py train2.py 特点&#xff1a;在执行完 train1.py之后再执行train2.py文件 …...

    使用lldb查看Rust不同类型的结构

    目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的&#xff0c;可以查看不同类型的结构&#xff0c;虽然这好像是C的东…...

    【Linux】线程POSIX信号量

    目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。但 POSIX可以用于线…...

    WPF中如何自定义控件

    WPF自定义控件简化版&#xff1a;账户菜单按钮&#xff08;AccountButton&#xff09; 我们以**“账户菜单按钮”为例&#xff0c;用更清晰的架构实现一个支持标题显示、渐变背景、选中状态高亮**的自定义控件。以下是分步拆解&#xff1a; 一、控件核心功能 我们要做一个类似…...

    大模型MCP更高效的通信:StreamableHTTP协议

    随着大语言模型&#xff08;LLMs&#xff09;的飞速发展&#xff0c;模型与应用之间的通信效率和灵活性变得至关重要。Model Context Protocol (MCP) 作为专为模型交互设计的协议&#xff0c;一直在不断进化以满足日益增长的需求。近期&#xff0c;MCP引入了一个令人振奋的新特…...

    防火墙在网络安全体系中的核心作用与原理

    防火墙在网络安全体系中的核心作用与原理 一、核心作用解析 1. 访问控制中枢 功能维度实现方式典型场景黑白名单控制基于IP/端口/协议的规则过滤限制外部IP访问财务系统&#xff0c;仅开放VPN端口权限分级用户组策略映射&#xff08;如AD集成&#xff09;禁止普通员工访问核心…...

    MySQL事务和JDBC中的事务操作

    一、什么是事务 事务是数据库操作的最小逻辑单元&#xff0c;具有"全有或全无"的特性。以银行转账为例&#xff1a; 典型场景&#xff1a; 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行&#xff0c;要么全部成功&#xff0c;要么全部失败…...

    每日脚本学习5.10 - XOR脚本

    xor运算的简介 异或就是对于二进制的数据可以 进行同0异1 简单的演示 &#xff1a; 结果是 这个就是异或 异或的作用 1、比较两数是否相等 2、可以进行加密 加密就是需要key 明文 :0b010110 key : 0b1010001 这个时候就能进行加密 明文 ^ key密文 还有这个加密比…...

    【编译原理】总结

    核心 闭包&#xff0c;正则闭包 产生式&#xff08;规则&#xff09; 文法 G[S](&#xff0c;&#xff0c;P&#xff0c;S) 一组规则的集合 &#xff1a;非终结符 &#xff1a;终结符 P&#xff1a;产生式 S&#xff1a;开始符号 推导 归约 规范&#xff08;最右&#xff…...

    docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

    备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…...

    OpenVLA:开源的视觉-语言-动作模型

    1. 简介 让我们先来介绍一下什么是OpenVLA&#xff0c;在这里&#xff1a; https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作&#xff08;VLA&#xff09;**模型。它是在 Open X-Embodiment 数据集 中的 97万…...

    Matlab/Simulink的一些功能用法笔记(4)

    水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置&#xff1a; ①点击预设 ②点击颜色&#xff0c;点击背景色的三角标符号 ③点击更多颜色&#xff0c;找到RGB选项 ④填写颜色参数…...

    【比赛真题解析】混合可乐

    这次给大家分享一道比赛题:混合可乐。 洛谷链接:U561549 混合可乐 【题目描述】 Jimmy 最近沉迷于可乐中无法自拔。 为了调配出他心目中最完美的可乐,Jimmy买来了三瓶不同品牌的可乐,然后立马喝掉了一些(他实在是忍不住了),所以 第一瓶可口可乐最大容量为 a 升,剩余 …...

    Elasticsearch:我们如何在全球范围内实现支付基础设施的现代化?

    作者&#xff1a;来自 Elastic Kelly Manrique SWIFT 和 Elastic 如何应对基础设施复杂性、误报问题以及日益增长的合规要求。 金融服务公司在全球范围内管理实时支付方面面临前所未有的挑战。SWIFT&#xff08;Society for Worldwide Interbank Financial Telecommunication -…...

    matlab介绍while函数

    MATLAB 中的 while 语句介绍 在 MATLAB 中&#xff0c;while 语句是一种循环结构&#xff0c;用于在满足特定条件时反复执行一段代码块。与 for 循环不同&#xff0c;while 循环的执行次数是动态的&#xff0c;取决于循环条件是否为真。 语法 while condition% 循环体代码 e…...

    如何解决 PowerShell 显示 “此系统上禁用了脚本运行” 的问题

    在 Windows 11 或 10 的 PowerShell 中运行脚本时,你可能会遇到一个错误,提示系统上禁用了脚本运行。这是一种安全功能,而不是系统问题,旨在防止可能有害的脚本自动运行。然而,如果你需要运行脚本来完成某些任务,或者你在系统上做了软件开发或测试的环境,那么你需要在 P…...

    深入浅出之STL源码分析4_类模版

    1.引言 我在上面的文章中讲解了vector的基本操作&#xff0c;然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0&#xff0c;而我们要讲解的是STL&#xff08;标准模板库&#xff09;&#xff0c;那么二者之间的关系是什么&#x…...

    探索科技的前沿动态:科技爱好者周刊

    探索科技的前沿动态:科技爱好者周刊 在信息爆炸的时代,我们每时每刻都被新技术、新理念包围。而如何在这纷繁复杂的信息中找到对自己有价值的内容,成了一大挑战。今天,我们要介绍的是一个宝贵的资源——科技爱好者周刊,它致力于为科技爱好者提供优质的科技资讯,每周五发…...

    初学者入门指南:什么是网络拓扑结构?

    初学者入门指南&#xff1a;什么是网络拓扑结构&#xff1f; 在构建或学习计算机网络时&#xff0c;一个绕不开的核心概念便是“网络拓扑结构”&#xff08;Network Topology&#xff09;。它决定了网络中各个设备如何连接、通信以及如何扩展。理解网络拓扑不仅有助于我们更清…...

    在 Vue 3 中实现刮刮乐抽奖

    &#x1f389; 在 Vue 3 中实现刮刮乐抽奖 当项目中需要做一些活动互动页时&#xff0c;需要实现刮刮乐&#xff0c;请看如下效果&#xff1a; 这里感谢github用户Choicc分享的组件&#xff0c;具体可点击传送门查看 1. 引入组件 将/src/components下ScratchCard.vue复制到自…...