數碼相框項目之顯示一張可放大、縮小、拖拽的圖片

之前我做過一個電子相框的項目,涉及到的重難點主要為:在LCD上放大、縮小、移動圖片。

首先我們得明白的一點是:無論是放大或縮小,實際上都是對原圖進行等比例的縮小,然后在LCD上面顯示,只不過縮小的程度不同罷了(關于如何取出原圖的數據,然后縮小為我們需要的大小,可以看我另外一篇博客:https://blog.csdn.net/qq_37659294/article/details/104382032)。

經過上一篇博客介紹的縮放算法之后,我們已經得到一張縮小后、和原圖等比例的圖片并存放在buffer中,但這只是得到一張圖片,還沒有顯示到LCD上,那么我們如何將這張圖片在LCD上動態地放大、縮小、移動呢?

?

首先,我們來看一下一個非常關鍵的函數,ShowZoomedPictureInLayout函數,這個函數是在LCD上顯示一張經過縮放、移動的圖片,因為圖片的大小可能比顯示區域要大,也有可能小,也有可能被移動到相框的邊緣,只能顯示圖片的一部分,所以我們必須確認要從圖片的哪里(iStartXofNewPic,iStartYofNewPic)開始取數據,在顯示區域的哪里(iStartXofOldPic,iStartYofOldPic)開始顯示,顯示多大(iWidthPictureInPlay,iHeightPictureInPlay)的區域。

ShowZoomedPictureInLayout函數的實現思路為:

①計算出iStartXofNewPic,iStartYofNewPic,判斷要從圖片的哪個位置開始取數據

②利用 g_iXofZoomedPicShowInCenter - iStartXofNewPic?= iDeltaX = 顯示區中心點X坐標(g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iStartXofOldPic(LCD上顯示圖片的起始坐標)等式算出iStartXofOldPic,iStartYofOldPic,判斷要從顯示區域的哪個位置開始顯示圖片

③計算出實際顯示的寬度iWidthPictureInPlay和高度iHeightPictureInPlay

④根據上面計算好的參數,把圖片數據刷到LCD的framebuffer中


/**********************************************************************
?* 函數名稱: ShowZoomedPictureInLayout
?* 功能描述: 在"manual頁面"中顯示經過縮放的圖片
?* 輸入參數: ptZoomedPicPixelDatas - 內含已經縮放的圖片的象素數據
?* ? ? ? ? ? ?ptVideoMem ? ? ? ? ? ?- 在這個VideoMem中顯示
?* 輸出參數: 無
?* 返 回 值: 無
?***********************************************************************/

static void ShowZoomedPictureInLayout(PT_PixelDatas ptZoomedPicPixelDatas, PT_VideoMem ptVideoMem)
{

? ? /* iStartXofNewPic和iStartYofNewPic這兩個變量意思不是新圖片在顯示區域(或者整個屏幕區域)的xy坐標
? ? ?* 而是從縮放后的圖片坐標(iStartXofNewPic,iStartYofNewPic)開始,顯示這個圖片
? ? ?* 坐標(x,y)之前的一塊區域,即橫坐標小于x,縱坐標小于y的所有地方,都不顯示
? ? ?* 因為可能縮放后的圖片,不能完全顯示到屏幕上,只能顯示一部分
? ? ?* 算出從圖片什么地方開始顯示,并且加上一個長和寬,那么在屏上顯示的部分就可以描繪出來了
? ? ?*/

? ? int iStartXofNewPic, iStartYofNewPic;

?? ?/* iStartXofOldPic和iStartYofOldPic這兩個變量并說不是上一張圖片的顯示位置,而是新圖片在屏幕顯示的起始坐標
?? ? * 這六個變量連起來想就是:
?? ? * 從zoom后的圖片的(iStartXofNewPic,iStartYofNewPic)這個地方開始
?? ? * 取長寬為iWidthPictureInPlay, iHeightPictureInPlay這么大的一塊區域
?? ? * 顯示到顯存的(iStartXofOldPic,iStartYofOldPic)這個地方去
?? ? */

? ? int iStartXofOldPic, iStartYofOldPic;
? ? int iWidthPictureInPlay, iHeightPictureInPlay;


? ? int iPictureLayoutWidth, iPictureLayoutHeight;
? ? int iDeltaX, iDeltaY;//計算過程的中間變量

?

?? ?/* iPictureLayoutWidth? 顯示區域的寬
?? ? * iPictureLayoutHeight 顯示區域的高
?? ? */

? ? iPictureLayoutWidth ?= g_tManualPictureLayout.iBotRightX - g_tManualPictureLayout.iTopLeftX + 1;
? ? iPictureLayoutHeight = g_tManualPictureLayout.iBotRightY - g_tManualPictureLayout.iTopLeftY + 1;
? ??
? ? /* g_iXofZoomedPicShowInCenter 是 圖片最左邊 到 顯示區中心 的距離,g_iXofZoomedPicShowInCenter = 縮放后寬的一半 + 移動的偏移量 (左移時為正,右移時為負(移動太大有可能變成負數的))
?? ? * 這個變量第一次賦值是在ShowPictureInManualPage(第一次顯示圖片(居中顯示),這時候圖片的大小比LCD的圖片顯示區域小)這個函數里,其值就是顯示圖片的寬的一半,也代表著最左邊到中心的距離
?? ? * 在不移動時,g_iXofZoomedPicShowInCenter值跟著放大和縮小相同的倍數
?? ? * 當移動的時候,在ManualPageRun函數里"移動圖片"的處理邏輯中,左移就增加,右移就減小,
?? ? * 同理 g_iYofZoomedPicShowInCenter 是圖片顯示位置最上面到顯示區域中心的距離
?? ? */

? ? iStartXofNewPic = g_iXofZoomedPicShowInCenter - iPictureLayoutWidth/2;//利用g_iXofZoomedPicShowInCenter算出iStartXofNewPic,便知道了要從圖片的哪個位置取出數據來顯示了,對應上面思路的第①步
? ? if (iStartXofNewPic < 0)//即g_iXofZoomedPicShowInCenter < iPictureLayoutWidth/2(顯示區寬的一半),說明圖片的最左邊在顯示區域內
? ? {
? ? ? ? iStartXofNewPic = 0;//如果圖片的最左邊在顯示區域內,那么就從圖片的最左邊開始取數據
? ? }

?? ?/* 這中間還有一種情況,就是
?? ? * 當 (iStartXofNewPic > 0) && (iStartXofNewPic < ptZoomedPicPixelDatas->iWidth) 時
?? ? * 表示 圖片最左邊 已經移出了顯示區域(最右邊還在顯示區域內),只能顯示一部分
?? ? * 此時 iStartXofNewPic = g_iXofZoomedPicShowInCenter - iPictureLayoutWidth/2;
?? ? * 表示就從圖片的這個地方開始顯示
?? ? */

? ? if (iStartXofNewPic > ptZoomedPicPixelDatas->iWidth)//即g_iXofZoomedPicShowInCenter >?ptZoomedPicPixelDatas->iWidth(圖寬) + iPictureLayoutWidth/2(顯示區寬的一半),說明圖片已經完全左移出去了
? ? {
? ? ? ? iStartXofNewPic = ptZoomedPicPixelDatas->iWidth;//整張圖片都已經被左移出顯示區域了,不用顯示了
? ? }

? ? ?/* iDeltaX 是 實際所顯示圖片的起始位置?到 顯示區域中心點 的距離,由g_iXofZoomedPicShowInCenter減去iStartXofNewPic得到,只是一個用來計算iStartXofOldPic的中間變量
? ? ?* 因為可能會移動到或者放大到左邊不能從頭開始,就是 0<iStartXofNewPic<ptZoomedPicPixelDatas->iWidth的情況了
? ? ?* 這個式子算出的是,實際圖片開始顯示的最左邊 到 顯示區域中心點的距離
? ? ?*
iDeltaX有可能是負數(當圖片移動到中心線右邊時,那么iDeltaX就是個負數了),但因為我們是利用數學等式來計算iStartXofOldPic,最后負負會得正
?? ? */
? ? iDeltaX = g_iXofZoomedPicShowInCenter - iStartXofNewPic;

? ? ?/* g_iXofZoomedPicShowInCenter - iStartXofNewPic
? ? ?* = iDeltaX
? ? ?* = 顯示區中心點X坐標(g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iStartXofOldPic(LCD上顯示圖片的起始坐標)
? ? ?* 根據上面的等式我們可算出?iStartXofOldPic

?? ? */
? ? iStartXofOldPic = (g_tManualPictureLayout.iTopLeftX + iPictureLayoutWidth / 2) - iDeltaX;


? ? ?/* 當 iStartXofNewPic = ptZoomedPicPixelDatas->iWidth 時,說明整張圖片已經從左邊劃出去了
?? ? * iDeltaX = g_iXofZoomedPicShowInCenter - ptZoomedPicPixelDatas->iWidth >= iPictureLayoutWidth / 2
?? ? * 所以iStartXofOldPic在這種情況下是可能小于g_tManualPictureLayout.iTopLeftX,所以需要判斷
?? ? */

? ? if (iStartXofOldPic < g_tManualPictureLayout.iTopLeftX)
? ? {
? ? ? ? iStartXofOldPic = g_tManualPictureLayout.iTopLeftX;
? ? }

? ? ?/* 向右邊劃出去了 */
? ? if (iStartXofOldPic > g_tManualPictureLayout.iBotRightX)
? ? {
? ? ? ? iStartXofOldPic = g_tManualPictureLayout.iBotRightX + 1;
? ? }
? ? ?

? ? ?/* ptZoomedPicPixelDatas->iWidth - iStartXofNewPic 是圖片除去了左邊可能不顯示的地方,剩下要顯示的部分
?? ? * g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1 是從顯示位置開始,到可以顯示圖片區域的結束,一共多寬
?? ? * 這兩個選一個小的,作為實際顯示的寬度
?? ? */

? ? if ((ptZoomedPicPixelDatas->iWidth - iStartXofNewPic) > (g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1))
? ? ? ? iWidthPictureInPlay = (g_tManualPictureLayout.iBotRightX - iStartXofOldPic + 1);
? ? else
? ? ? ? iWidthPictureInPlay = (ptZoomedPicPixelDatas->iWidth - iStartXofNewPic);
? ??

? ? ?/* 上面是關于X坐標的計算,下面開始Y方向的計算,原理一樣 */
? ? iStartYofNewPic = g_iYofZoomedPicShowInCenter - iPictureLayoutHeight/2;
? ? if (iStartYofNewPic < 0)
? ? {
? ? ? ? iStartYofNewPic = 0;
? ? }
? ? if (iStartYofNewPic > ptZoomedPicPixelDatas->iHeight)
? ? {
? ? ? ? iStartYofNewPic = ptZoomedPicPixelDatas->iHeight;
? ? }
? ? iDeltaY = g_iYofZoomedPicShowInCenter - iStartYofNewPic;
? ? iStartYofOldPic = (g_tManualPictureLayout.iTopLeftY + iPictureLayoutHeight / 2) - iDeltaY;

? ? if (iStartYofOldPic < g_tManualPictureLayout.iTopLeftY)
? ? {
? ? ? ? iStartYofOldPic = g_tManualPictureLayout.iTopLeftY;
? ? }
? ? if (iStartYofOldPic > g_tManualPictureLayout.iBotRightY)
? ? {
? ? ? ? iStartYofOldPic = g_tManualPictureLayout.iBotRightY + 1;
? ? }
? ??
? ? if ((ptZoomedPicPixelDatas->iHeight - iStartYofNewPic) > (g_tManualPictureLayout.iBotRightY - iStartYofOldPic + 1))
? ? {
? ? ? ? iHeightPictureInPlay = (g_tManualPictureLayout.iBotRightY - iStartYofOldPic + 1);
? ? }
? ? else
? ? {
? ? ? ? iHeightPictureInPlay = (ptZoomedPicPixelDatas->iHeight - iStartYofNewPic);
? ? }
? ?

? ? ?/* 把整個圖片顯示區填充為背景色 */? ??
? ? ClearVideoMemRegion(ptVideoMem, &g_tManualPictureLayout, COLOR_BACKGROUND);

? ? ?/* 傳入前面計算好的參數,顯示縮放、移動后的圖片
?? ? * iStartXofNewPic, iStartYofNewPic? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?:從圖片的哪個位置開始取數據
?? ? * iStartXofOldPic, iStartYofOldPic? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :在LCD的哪個位置開始顯示圖片
?? ? * iWidthPictureInPlay, iHeightPictureInPlay ? ? ? ? ? ? ? ? ? :正真顯示圖片的寬和高
?? ? * ptZoomedPicPixelDatas, &ptVideoMem->tPixelDatas:源和目的
?? ? */

? ? PicMergeRegion(iStartXofNewPic, iStartYofNewPic, iStartXofOldPic, iStartYofOldPic, iWidthPictureInPlay, iHeightPictureInPlay, ptZoomedPicPixelDatas, &ptVideoMem->tPixelDatas);
}

?

PicMergeRegion函數定義如下:

/*********************************************************************** 函數名稱: PicMergeRegion* 功能描述: 把新圖片的某部分, 合并入老圖片的指定區域* 輸入參數: iStartXofNewPic, iStartYofNewPic : 從新圖片的(iStartXofNewPic, iStartYofNewPic)座標處開始讀出數據用于合并*            iStartXofOldPic, iStartYofOldPic : 合并到老圖片的(iStartXofOldPic, iStartYofOldPic)座標去*            iWidth, iHeight                  : 合并區域的大小*            ptNewPic                         : 新圖片*            ptOldPic                         : 老圖片* 輸出參數: 無* 返 回 值: 0 - 成功, 其他值 - 失敗***********************************************************************/
int PicMergeRegion(int iStartXofNewPic, int iStartYofNewPic, int iStartXofOldPic, int iStartYofOldPic, int iWidth, int iHeight, PT_PixelDatas ptNewPic, PT_PixelDatas ptOldPic)
{int i;unsigned char *pucSrc;unsigned char *pucDst;int iLineBytesCpy = iWidth * ptNewPic->iBpp / 8;if ((iStartXofNewPic < 0 || iStartXofNewPic >= ptNewPic->iWidth) || \(iStartYofNewPic < 0 || iStartYofNewPic >= ptNewPic->iHeight) || \(iStartXofOldPic < 0 || iStartXofOldPic >= ptOldPic->iWidth) || \(iStartYofOldPic < 0 || iStartYofOldPic >= ptOldPic->iHeight)){return -1;}pucSrc = ptNewPic->aucPixelDatas + iStartYofNewPic * ptNewPic->iLineBytes + iStartXofNewPic * ptNewPic->iBpp / 8;pucDst = ptOldPic->aucPixelDatas + iStartYofOldPic * ptOldPic->iLineBytes + iStartXofOldPic * ptOldPic->iBpp / 8;for (i = 0; i < iHeight; i++){memcpy(pucDst, pucSrc, iLineBytesCpy);pucSrc += ptNewPic->iLineBytes;pucDst += ptOldPic->iLineBytes;}return 0;
}

?

?

下面是放大的完整處理過程:

static int g_iXofZoomedPicShowInCenter;  
static int g_iYofZoomedPicShowInCenter;
/* 放大/縮小系數 */
#define ZOOM_RATIO (0.9)
case 2: /* 放大按鈕 */
{/* 獲得放大后的數據 */iZoomedWidth  = (float)g_tZoomedPicPixelDatas.iWidth / ZOOM_RATIO;iZoomedHeight = (float)g_tZoomedPicPixelDatas.iHeight / ZOOM_RATIO;ptZoomedPicPixelDatas = GetZoomedPicPixelDatas(&g_tOriginPicPixelDatas, iZoomedWidth, iZoomedHeight);/* 重新計算中心點 */g_iXofZoomedPicShowInCenter = (float)g_iXofZoomedPicShowInCenter / ZOOM_RATIO;g_iYofZoomedPicShowInCenter = (float)g_iYofZoomedPicShowInCenter / ZOOM_RATIO;/* 顯示新數據 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);break;
}

縮小:

/* 放大/縮小系數 */
#define ZOOM_RATIO (0.9)
case 1: /* 縮小按鈕 */
{/* 獲得縮小后的數據 */iZoomedWidth  = (float)g_tZoomedPicPixelDatas.iWidth * ZOOM_RATIO;iZoomedHeight = (float)g_tZoomedPicPixelDatas.iHeight * ZOOM_RATIO;ptZoomedPicPixelDatas = GetZoomedPicPixelDatas(&g_tOriginPicPixelDatas, iZoomedWidth, iZoomedHeight);/* 重新計算中心點 */g_iXofZoomedPicShowInCenter = (float)g_iXofZoomedPicShowInCenter * ZOOM_RATIO;g_iYofZoomedPicShowInCenter = (float)g_iYofZoomedPicShowInCenter * ZOOM_RATIO;/* 顯示新數據 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);break;
}

拖拽:

/* 如果觸點滑動距離大于規定值, 則挪動圖片 */
if (DistanceBetweenTwoPoint(&tInputEvent, &tPreInputEvent) > SLIP_MIN_DISTANCE)
{                            /* 重新計算中心點 */g_iXofZoomedPicShowInCenter -= (tInputEvent.iX - tPreInputEvent.iX);g_iYofZoomedPicShowInCenter -= (tInputEvent.iY - tPreInputEvent.iY);/* 顯示新數據 */ShowZoomedPictureInLayout(ptZoomedPicPixelDatas, ptDevVideoMem);/* 記錄上次滑動點 */tPreInputEvent = tInputEvent;                            
}

實驗效果:

一開始顯示是居中顯示

拖拽?

放大?

參考文章:https://blog.csdn.net/qq_22655017/article/details/97627382

?

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

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

相關文章

TCP協議-如何保證傳輸可靠性

TCP協議傳輸的特點主要就是面向字節流、傳輸可靠、面向連接。這篇博客&#xff0c;我們就重點討論一下TCP協議如何確保傳輸的可靠性的。 確保傳輸可靠性的方式 TCP協議保證數據傳輸可靠性的方式主要有&#xff1a; 校驗和序列號確認應答超時重傳連接管理流量控制擁塞控制 校…

TCP協議-握手與揮手

認識TCP協議 TCP全稱為“傳輸控制協議”&#xff0c;這是傳輸層的一個協議&#xff0c;對數據的傳輸進行一個詳細的控制。 特點&#xff1a; 面向字節流安全可靠面向連接 TCP協議段格式 源端口號與目的端口號&#xff1a;這里與UDP的一樣&#xff0c;每個數據都要知道從哪個…

ASOC注冊過程

一、什么是ASOC 在嵌入式系統里面的聲卡驅動為ASOC&#xff08;ALSA System on Chip&#xff09; &#xff0c;它是在ALSA 驅動程序上封裝的一層&#xff0c;分為3大部分&#xff0c;Machine&#xff0c;Platform和Codec ,三部分的關系如下圖所示&#xff1a;其中Machine是指我…

ASOC調用過程

上一篇文章我們將了嵌入式系統注冊聲卡的過程&#xff1a;https://blog.csdn.net/qq_37659294/article/details/104748747 這篇文章我們以打開一個聲卡的播放節點為例&#xff0c;講解一下在APP調用open時&#xff0c;最終會如何調用到硬件相關的函數。 在上一篇文章最后我們說…

編寫聲卡驅動(框架)

在前面兩篇文章中&#xff0c;我們分別講了嵌入式Linux系統聲卡注冊的過程和調用的過程&#xff1a; https://blog.csdn.net/qq_37659294/article/details/104748747 https://blog.csdn.net/qq_37659294/article/details/104802868 講了那么多&#xff0c;我們最終的目的無非…

聲卡學習筆記

分享幾篇關于韋東山聲卡驅動的學習筆記&#xff0c;作者寫得非常詳細。 ALSA驅動框架&#xff1a;https://blog.csdn.net/qingkongyeyue/article/details/52328991 ASoC驅動框架&#xff1a;https://blog.csdn.net/qingkongyeyue/article/details/52349120 ASoC驅動重要結構…

路由器、交換機、集線器的區別

https://blog.csdn.net/weibo1230123/article/details/82779040

$PATH環境變量的作用

echo $PATH 顯示當前PATH環境變量&#xff0c;該變量的值由一系列以冒號分隔的目錄名組成&#xff0c;如&#xff1a;/usr/local/bin:/bin:/usr/bin。(冒號:是路徑分隔符) 在執行一個程序的時候如果沒有PATH的話&#xff0c;就需要寫出路徑名&#xff08;絕對或者相對&#xf…

dmesg

https://blog.csdn.net/zm_21/article/details/31760569

進程上下文與中斷上下文的理解

一.什么是內核態和用戶態 內核態&#xff1a;在內核空間執行&#xff0c;通常是驅動程序&#xff0c;中斷相關程序&#xff0c;內核調度程序&#xff0c;內存管理及其操作程序。 用戶態&#xff1a;用戶程序運行空間。 二.什么是進程上下文與中斷上下文 1.進程上下文&#xf…

GDB調試教程:1小時玩轉Linux gdb命令

原文鏈接&#xff1a;http://c.biancheng.net/gdb/ GDB 入門教程 本教程以下面的代碼為例&#xff0c;在 Linux 系統下來講解 GBD 的調試流程&#xff1a; int main (void) {unsigned long long int n, sum;n 1;sum 0;while (n < 100){sum sum n;n n 1;}return 0; …

shell將命令執行的結果賦值給 變量

https://blog.csdn.net/lemontree1945/article/details/79126819/

Linux下shell腳本指定程序運行時長

https://www.cnblogs.com/yychuyu/p/12626798.html

vim編輯器如何刪除一行或者多行內容

http://blog.itpub.net/69955379/viewspace-2681334/

C++經典問題:如果對象A中有對象成員B,對象B沒有默認構造函數,那么對象A必須在初始化列表中初始化對象B?

對象成員特點總結&#xff1a; &#xff08;1&#xff09;實例化對象A時&#xff0c;如果對象A有對象成員B,那么先執行對象B的構造函數&#xff0c;再執行A的構造函數。 &#xff08;2&#xff09;如果對象A中有對象成員B,那么銷毀對象A時&#xff0c;先執行對象A的析構函數&…

JZ2440用U-Boot給Nand-Flash燒寫程序時報錯:NAND write: incorrect device type in bootloader ‘bootloader‘ is not

JZ2440開發板使用問題&#xff0c;U-Boot燒寫程序到Nand Flash時報錯&#xff1a;NAND write: incorrect device type in bootloader bootloader is not a number 這是因為分區名中u-boot&#xff0c;不是bootloader&#xff0c;而cmd_menu.c里用的是bootloader 可以執行&#…

韋東山銜接班——4.4_構建根文件系統之構建根文件系統

文章地址&#xff1a; https://blog.csdn.net/gongweidi/article/details/100086289?biz_id102&utm_term%E9%9F%A6%E4%B8%9C%E5%B1%B1%E8%A1%94%E6%8E%A5%E7%8F%AD&utm_mediumdistribute.pc_search_result.none-task-blog-2~blog~sobaiduweb~default-5-100086289&…

C++中const char *p和char const *p

const char *p;他的意思是p指向的目標空間的內容不可變化 例如定義char cA; p&c;則c的內容不可以變化.如cB;等一些企圖改變變量c的值的做法都不行. 然而p仍然是動態的,就是它還可以指向別的空間,被賦予新的地址值,只是被他指向的目標空間的內容不可變化,如上面的c值始終為A…

qt 分割字符串的兩種方法

https://blog.csdn.net/a724699769/article/details/62216435

【YOLO系列】YOLOv3代碼詳解(五):utils.py腳本

前言 以下內容僅為個人在學習人工智能中所記錄的筆記&#xff0c;先將目標識別算法yolo系列的整理出來分享給大家&#xff0c;供大家學習參考。 本文僅對YOLOV3代碼中關鍵部分進行了注釋&#xff0c;未掌握基礎代碼的鐵汁可以自己百度一下。 若文中內容有誤&#xff0c;希望大家…