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函數來獲取圖像,這里就不進一步說明了。可以參考官方對于不同格式圖像的解碼器例程代碼。

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/news/904936.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/news/904936.shtml
    英文地址,請注明出處:http://en.pswp.cn/news/904936.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    【Web前端開發】HTML基礎

    Web前端開發是用來直接給用戶呈現一個一個的網頁&#xff0c;主要包含實現用戶的結構&#xff08;HTML&#xff09;、樣式&#xff08;CSS&#xff09;、交互&#xff08;JavaScript&#xff09;。然而一個軟件通常是由后端和前端完成的。可以查閱文檔&#xff1a;HTML 教程 (w…

    MySQL 8.0 單節點部署與一主兩從架構搭建實戰

    前言&#xff1a;在數據驅動的時代&#xff0c;數據庫作為數據存儲與管理的核心組件&#xff0c;其架構的選擇與配置對系統的性能、可用性和擴展性至關重要。MySQL 作為一款廣泛應用的開源關系型數據庫&#xff0c;憑借其穩定的性能和豐富的功能&#xff0c;深受開發者和企業的…

    數據庫故障排查全攻略:從實戰案例到體系化解決方案

    一、引言&#xff1a;數據庫故障為何是技術人必須攻克的 "心腹大患" 在數字化時代&#xff0c;數據庫作為企業核心數據資產的載體&#xff0c;其穩定性直接決定業務連續性。據 Gartner 統計&#xff0c;企業每小時數據庫 downtime 平均損失高達 56 萬美元&#xff0…

    牛客周賽round91

    C 若序列為1 4 5 7 9 1 2 3&#xff0c;1 9一定大于1 1或1 4...所以只需要記錄當前數之前數字的最大值&#xff0c;然后遍歷取max即可&#xff0c;所以對于上面的序列有效的比較為1 9&#xff0c;2 9&#xff0c;3 9取max 代碼 //求大于當前數的最大值&#xff0c;然后…

    【MCAL】TC397+EB-tresos之I2c配置實戰(同步、異步)

    I2C總線是Philips公司在八十年代初推出的一種串行、半雙工的總線&#xff0c;主要用于近距離、低速的芯片之間的通信。本篇文章首先從理論講起&#xff0c;介紹了英飛凌TC3x系列芯片對應MCAL中對I2C驅動的定義與介紹&#xff0c;建議讀者在閱讀本篇文章之前對I2C有個簡單的認識…

    深拷貝與淺拷貝:理解 Python 中的對象復制機制

    深拷貝與淺拷貝&#xff1a;理解 Python 中的對象復制機制 在 Python 編程中&#xff0c;對象的復制是一個常見的操作。然而&#xff0c;很多初學者在處理對象復制時會遇到困惑&#xff0c;尤其是在涉及到復雜數據結構&#xff08;如列表、字典、自定義對象等&#xff09;時。…

    BeanPostProcessor和AOP

    BeanPostProcessor Spring中有一個接口Oredr的getOrder()方法&#xff0c;這個方法返回值是一個int類型&#xff0c;Spring容器會根據這個方法的返回值 對容器的多個Processor對象從小到大排序&#xff0c;創建Bean時候依次執行他們的方法&#xff0c;也就是說getOrder()方法的…

    拒絕服務攻擊(DoS/DDoS/DRDoS)詳解:洪水猛獸的防御之道

    在數字時代&#xff0c;服務的可用性是衡量一個在線系統成功與否的關鍵指標之一。然而&#xff0c;存在一類被稱為"拒絕服務攻擊" (Denial of Service, DoS) 的網絡攻擊&#xff0c;其主要目的就是通過各種手段耗盡目標服務器或網絡的資源&#xff0c;使其無法響應正…

    小剛說C語言刷題—1078求恰好使s=1+1/2+1/3+…+1/n的值大于X時n的值

    1.題目描述 求恰好使 s11/21/3?1/n 的值大于 X 時 n 的值。( 2≤x≤10 ) 輸入 輸入只有一行&#xff0c;包括 1個整數 X 。 輸出 輸出只有一行&#xff08;這意味著末尾有一個回車符號&#xff09;&#xff0c;包括 1 個整數。 樣例 輸入 2 輸出 4 2.參考代碼(C語言…

    深度學習中的目標檢測:從 PR 曲線到 AP

    深度學習中的目標檢測&#xff1a;從 PR 曲線到 AP 在目標檢測任務中&#xff0c;評估模型的性能是非常重要的。通過使用不同的評估指標和標準&#xff0c;我們可以量化模型的準確性與效果。今天我們將重點討論 PR 曲線&#xff08;Precision-Recall Curve&#xff09;、平均精…

    MySQL 1366 - Incorrect string value:錯誤

    MySQL 1366 - Incorrect string value:錯誤 錯誤如何發生發生原因&#xff1a; 解決方法第一種嘗試第二種嘗試 錯誤 如何發生 在給MySQL添加數據的時候發生了下面的錯誤 insert into sys_dept values(100, 0, 0, 若依科技, 0, 若依, 15888888888, ryqq.com, 0,…

    [ctfshow web入門] web70

    信息收集 使用cinclude("php://filter/convert.base64-encode/resourceindex.php");讀取的index.php error_reporting和ini_set被禁用了&#xff0c;不必管他 error_reporting(0); ini_set(display_errors, 0); // 你們在炫技嗎&#xff1f; if(isset($_POST[c])){…

    Linux在web下http加密和配置虛擬主機及動態頁面發布

    web服務器的數據加密 1.簡介&#xff1a;由于http協議以明文方式發送&#xff0c;不提供任何方式的數據加密&#xff0c;也不適合傳輸一些重要的信息&#xff0c;如銀行卡號、密碼等&#xff0c;解決該缺陷設計了安全套接字層超文本傳輸協議https&#xff1b; 2.https的握手流…

    uni-app,小程序中的addPhoneContact,保存聯系人到手機通訊錄

    文章目錄 方法詳解簡介 基本語法參數說明基礎用法使用示例平臺差異說明注意事項最佳實踐 方法詳解 簡介 addPhoneContact是uni-app框架提供的一個實用API&#xff0c;用于向系統通訊錄添加聯系人信息。這個方法在需要將應用內的聯系人信息快速保存到用戶設備通訊錄的場景下非…

    NHANES稀有指標推薦:HALP score

    文章題目&#xff1a;Associations of HALP score with serum prostate-specific antigen and mortality in middle-aged and elderly individuals without prostate cancer DOI&#xff1a;10.3389/fonc.2024.1419310 中文標題&#xff1a;HALP 評分與無前列腺癌的中老年人血清…

    【django.db.utils.OperationalError: unable to open database file】

    解決platform.sh 環境下&#xff0c;無法打開數據庫問題 場景 在platform.sh 執行python manage.py createsuperuser是提示 django.db.utils.OperationalError: unable to open database file 錯誤 原因 由于settings.py文件中 本地數據庫配置在線上配置后&#xff0c;導致…

    【前端分享】CSS實現3種翻頁效果類型,附源碼!

    使用 css 可以實現多種翻頁效果&#xff0c;比如書本翻頁、卡片翻轉等。以下是兩種常見的翻頁效果實現&#xff1a; 效果 1&#xff1a;書本翻頁效果 通過 transform 和 rotateY 實現 3D 翻頁效果。 html 結構 <divclass"book"> <divclass"page pa…

    【部署滿血Deepseek-R1/V3】大型語言模型部署實戰:多機多卡DeepSeek-R1配置指南

    大家好&#xff01;這里是迪小莫學AI&#xff0c;今天的文章是“”大型語言模型部署實戰&#xff1a;多機多卡DeepSeek-R1配置指南“” 前言 隨著大型語言模型的快速發展&#xff0c;如何高效部署這些模型成為技術團隊面臨的重要挑戰。本文將分享基于DeepSeek-R1模型的多機多…

    IPM IMI111T-026H 高效風扇控制板

    概述&#xff1a; REF-MHA50WIMI111T 是一款專為風扇驅動設計的參考開發板&#xff0c;搭載了英飛凌的IMI111T-026H iMOTION?智能功率模塊(IPM)。這個模塊集成了運動控制引擎(MCE)、三相柵極驅動器和基于IGBT的功率級&#xff0c;全部封裝在一個緊湊的DSO22封裝中。REF-MHA50…

    Linux 阻塞和非阻塞 I/O 簡明指南

    目錄 聲明 1. 阻塞和非阻塞簡介 2. 等待隊列 2.1 等待隊列頭 2.2 等待隊列項 2.3 將隊列項添加/移除等待隊列頭 2.4 等待喚醒 2.5 等待事件 3. 輪詢 3.1 select函數 3.2 poll函數 3.3 epoll函數 4. Linux 驅動下的 poll 操作函數 聲明 本博客所記錄的關于正點原子…