目錄開源庫移植步驟[1]:下載庫的源碼包[2]:解壓,且閱讀“README(自述文件)",瞭解對應庫的使用規則[3]:打開源碼中的install.txt的文本,學習庫的移植和安裝步驟[4]:把下載好的源碼包jpegsrc.v9f.tar.gz發送到linux系統的家目錄下進行解壓[5] :“配置” ...
目錄
- 開源庫移植步驟
- 庫的使用
- 解碼步驟
- 習題:設計程式實現在LCD上的任意位置顯示一張任意大小的jpg圖片,註意不要越界。
- [1]:創建解碼對象,並且對解碼對象進行初始化,另外需要創建錯誤處理對象,並和解碼對象進行關聯
- [2]:打開待解碼的jpg圖片,使用fopen的時候需要添加選項”b”,以二進位方式打開文件!
- [3]:讀取待解碼圖片的文件頭,並把圖像信息和解碼對象進行關聯,通過解碼對象對jpg圖片進行解碼
- [4]:可以選擇設置解碼參數,如果打算以預設參數對jpg圖片進行解碼,則可以省略該步驟!
- [5]:開始對jpg圖片進行解碼,調用函數之後開始解碼,可以得到圖像寬、圖像高等信息!
- [6]:開始設計一個迴圈,在迴圈中每次讀取1行的圖像數據,並寫入到LCD中
- [7]:在所有的圖像數據都已經解碼完成後,則調用函數完成解碼即可,然後釋放相關資源!(不要遺漏打開的圖像文件-----fclose(infile) )
- [8]: 將所有頭文件和庫文件與main.c 一同完成編譯
- 代碼完整展示
- 解碼步驟
開源庫移植步驟
註:以下操作均以 JPEG圖片操作 為例
[1]:下載庫的源碼包
-
從對應庫的官網下載庫的源碼包,可直接搜索”lib關鍵字“,如”libJPEG“
-
下載時,最好選擇全英文網站進行下載,優先選擇代碼溝通網站【github、sourceforge、git】,一般可以選擇以”.org“結尾的網址(xxx.sourceforge.org)
-
根據不同的系統平臺,選擇不同的壓縮形式的源碼包
例:Windows平臺---> xxx.zip Linux平臺---> xxx.tar.gz
網址為:JPEG庫源碼包下載
[2]:解壓,且閱讀“README(自述文件)",瞭解對應庫的使用規則
-
此時是為了閱讀“README”,所以可以直接在 Windows下直接解壓源碼包
-
閱讀順序一般為:
- install.txt: 學習庫的移植和安裝步驟
- libjpeg.txt: 學習庫的使用方法和步驟
- example.c: 在瞭解庫的基本使用方法後,可以依照給出的庫使用例子,進行模仿使用該庫
[3]:打開源碼中的install.txt的文本,學習庫的移植和安裝步驟
-
經過閱讀文本可知:(重要且背誦)
移植libjpeg的步驟分為三步:配置(./configure) + 編譯(make) + 安裝(make install)。
[4]:把下載好的源碼包jpegsrc.v9f.tar.gz發送到linux系統的家目錄下進行解壓
- 註意不可以在共用文件夾進行解壓,因為共用文件夾依然屬於Windows平臺環境
[5] :“配置” (./configure)
切換到解壓後的jpeg-9f的文件夾內,進行“配置”操作,且依照實際需要修改configure的參數(數據存放路徑 + 指定平臺)
- 若是直接使用 ./configure ,系統會按照預設參數進行配置,不一定符合我們實際所需,所以需要使用指令對configure的參數進行修改
- 預設參數指的是:編輯器使用---gcc 、 數據存放路徑---/usr/local 等
- 可以使用 "./configure --help" 查看所有可改的選項
- 我們一般需要修改 數據存放路徑 和 指定目標平臺 兩個參數。代碼如下:
修改路徑: --prefix = [絕對路徑]
指定目標平臺:--host = arm-linux (不用加gcc,因為此時修改的是平臺;且一般修改平臺後,編輯器也會隨之修改)
- 配置成功後,會得到一個“Makefile”腳本文件,為下一步“編譯”做準備
[6]: “編譯” (make)
配置成功之後,會得到一個makefile腳本文件,此時可以完成移植的第二步:*編譯*,在命令行輸入指令:make ,該指令會自動執行makefile
-
“編譯”過程中不可以有錯誤(出現error),如果出錯,則需要重新進行“配置”
-
如果出現下圖情況,是因為沒有安裝“make”工具所導致的,只需安裝一下解決
[7]:“安裝” (make install)
**編譯通過之後,則可以完成libjpeg庫的*安裝*,此時在命令行輸入指令: make install **
- “安裝”過程中也不可以有錯誤(出現error),如果出錯,則需要重新進行“配置”
- "安裝"生成的文件會被放置到"配置"過程中指定的路徑下
即 頭文件----> [指定路徑]/include
庫文件----> [指定路徑]/lib
[8]:安裝完成後,拷貝用戶指定的安裝路徑中生成的libjpeg庫的頭文件和庫文件,方便後續設計程式使用
[9]:把include文件夾和lib文件夾與自己的工程文件放在同一個路徑,方便後期的工程維護!
庫的使用
閱讀libjpeg.txt相關信息,瞭解並學習JPEG圖片的解碼與編碼過程
解碼步驟
為了可以把一張jpg圖片顯示在LCD上,所以需要把jpg圖片進行解壓,解壓之後就可以得到圖片內部的像素點的顏色分量,就可以把像素點的顏色分量向LCD的像素點寫入。就需要掌握jpg圖片的解壓流程(背下來)。藉助習題來更好的理解解壓流程:
習題:設計程式實現在LCD上的任意位置顯示一張任意大小的jpg圖片,註意不要越界。
[1]:創建解碼對象,並且對解碼對象進行初始化,另外需要創建錯誤處理對象,並和解碼對象進行關聯
- 解碼對象一般命名為 cinfo,且解碼對象是一個結構體變數
- 調用“jpeg_create_decompress(&cinfo)"完成對解碼對象的初始化
代碼展示:
/*[1]:創建解碼對象,並且對解碼對象進行初始化,另外需要創建錯誤處理對象,並和解碼對象進行關聯*/
//創建解碼對象,其是一個結構體變數
struct jpeg_decompress_struct cinfo;
//創建錯誤處理對象
struct jpeg_error_mgr jerr;
//將錯誤處理對象與解碼對象相關聯
cinfo.err = jpeg_std_error(&jerr);
[2]:打開待解碼的jpg圖片,使用fopen的時候需要添加選項”b”,以二進位方式打開文件!
- 需要把打開的文件的文件指針和解碼對象進行綁定
代碼展示:
/*[2]:打開待解碼的jpg圖片,使用fopen的時候需要添加選項”b”,以二進位方式打開文件!*/
FILE * infile; //接收打開文件的文件指針
unsigned char * buffer; //輸出行緩衝區
int row_stride; //buffer一行的像素點數量,即圖片的寬度
// 以二進位方式打開圖片,併進行錯誤處理
if ((infile = fopen(filename, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", filename);
return 0;
}
//把打開的文件的文件指針和解碼對象進行綁定
jpeg_stdio_src(&cinfo, infile);
[3]:讀取待解碼圖片的文件頭,並把圖像信息和解碼對象進行關聯,通過解碼對象對jpg圖片進行解碼
代碼展示:
/*[3]:讀取待解碼圖片的文件頭,並把圖像信息和解碼對象進行關聯,通過解碼對象對jpg圖片進行解碼*/
(void) jpeg_read_header(&cinfo, TRUE);
[4]:可以選擇設置解碼參數,如果打算以預設參數對jpg圖片進行解碼,則可以省略該步驟!
- 使用預設參數的情況,即對jpg圖片的操作不涉及縮放圖片大小等操作
代碼展示:
/*[4]:可以選擇設置解碼參數,如果打算以預設參數對jpg圖片進行解碼,則可以省略該步驟!*/
/* 在該習題要求中,並不涉及圖片縮放等問題,所以我們可以省略該步驟
* jpeg_read_header(),
*/
[5]:開始對jpg圖片進行解碼,調用函數之後開始解碼,可以得到圖像寬、圖像高等信息!
- 圖像寬: output_width
- 圖像寬: output_height
- 圖像色深:output_components
註意:此處的色深數據是以“位元組”為單位存儲的
代碼展示:
/*[5]:開始對jpg圖片進行解碼,調用函數之後開始解碼,可以得到圖像寬、圖像高等信息!*/
//我們只需要調用該函數,將圖像信息放入解碼對象中,無需註意其的返回值
(void) jpeg_start_decompress(&cinfo);
[6]:開始設計一個迴圈,在迴圈中每次讀取1行的圖像數據,並寫入到LCD中
-
註意:轉換演算法需要用戶自己依照要求設計。
-
該題目需要任意位置顯示,所以在“寫入”時需要考慮圖片的初始位置
-
由於該題目的圖片大小也是未知的,所以在程式中必須調用解碼對象中的圖片信息,而不能將條件”寫死“
-
(void) jpeg_read_scanlines(&cinfo, &buffer, 1) 函數返回的是實際讀取的行的數量
-
cinfo.output_scanline 該變數記錄的是掃描行的數量,初始值為0,調用一次"jpeg_read_scanlines()"函數,數值加1
-
JPEG存儲的方式與BMP不同,JPEG的存儲方式是大端存儲。即 像素點的顏色順序位 RGB; 存儲的圖像行數據 是從上到下
-
申請緩衝區大小時,不應該申請超過圖像一行數據大小的空間
代碼展示:
*[6]:開始設計一個迴圈,在迴圈中每次讀取1行的圖像數據,並寫入到LCD中*/
//計算圖像一行的大小
row_stride = cinfo.output_width * cinfo.output_components;
//為自定義緩衝區申請堆記憶體,註意申請的記憶體空間大小應為圖像一行的大小
buffer = calloc(1,row_stride);
//定義一個int類型變數,用於存放顏色分量數據
int data = 0;
/*定義一個迴圈,用於迴圈寫入一行的圖像數據;
使用解碼對象當前掃描行數與圖像的高比較結果作為迴圈條件,當兩者相等,即圖像數據寫入完後退出迴圈*/
while (cinfo.output_scanline < cinfo.output_height)
{
/*調用jpeg_read_scanlines函數,讀取解碼對象中的圖像一行數據,並存放進自定義緩衝區中
且cinfo.output_scanline會隨著調用該函數而增加1,保證while迴圈能夠正常退出*/
(void) jpeg_read_scanlines(&cinfo, &buffer, 1); //從上到下,從左到右 RGB RGB RGB RGB
//將緩衝區中存儲的數據逐一寫入LCD的記憶體映射空間中
for (int i = 0; i < cinfo.output_width; ++i) //012 345
{
/*由於圖片沒有透明度,所以一個像素點大小為3byte,而data為int類型變數,所以需要
藉助"|=" 使得顏色分量順序存儲正確;又因為JEPG存儲顏色分量順序為RGB,所以進行下麵演算法*/
data |= buffer[3*i]<<16; //R
data |= buffer[3*i+1]<<8; //G
data |= buffer[3*i+2]; //B
/*把像素點寫入到LCD的指定位置。其中800*start_y + start_x控制的是用戶自定義的圖片顯示初始位置;
800*(cinfo.output_scanline-1)控制的是寫入圖像數據的行數切換;+ i控制的是寫入圖像數據的列數切換*/
lcd_mp[800*start_y + start_x + 800*(cinfo.output_scanline-1) + i] = data;
//最後需將data內部清零,避免對下一次迴圈的顏色分量寫入造成影響
data = 0;
}
}
[7]:在所有的圖像數據都已經解碼完成後,則調用函數完成解碼即可,然後釋放相關資源!(不要遺漏打開的圖像文件-----fclose(infile) )
代碼展示:
/*[7]:在所有的圖像數據都已經解碼完成後,則調用函數完成解碼即可,然後釋放相關資源!(不要遺漏打開的圖像文件)*/
//解碼完成
(void) jpeg_finish_decompress(&cinfo);
//釋放解碼對象
jpeg_destroy_decompress(&cinfo);
//關閉打開的圖像文件
fclose(infile);
return 1;
[8]: 將所有頭文件和庫文件與main.c 一同完成編譯
錯誤情況分析:
代碼完整展示
/*******************************************************************
*
* file name: main.c
* author : [email protected]
* date : 2024/05/13
* function : 該案例是掌握JPEG的解碼過程
* note : None
*
* CopyRight (c) 2023-2024 [email protected] All Right Reseverd
*
* *****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include "jpeglib.h"
/********************************************************************
*
* name : read_JPEG_file
* function : 實現
* argument : 完成libjpeg庫的移植,實現在LCD上的任意位置
顯示一張任意大小的jpg圖片,並且對可能越界的情況做錯誤處理。
* @filename :需要解碼的jpg圖片
@start_x :圖片顯示初始位置的橫坐標
@start_y :圖片顯示初始位置的縱坐標
@lcd_mp :LCD屏記憶體映射空間的地址
*
* retval : 調用成功返回1,調用失敗返回0
* author : [email protected]
* date : 2024/05/13
* note : 學習JPEG的解碼過程,以及JPEG存儲顏色分量的方式
*
* *****************************************************************/
int read_JPEG_file (char * filename,int start_x,int start_y,int * lcd_mp)
{
/*[1]:創建解碼對象,並且對解碼對象進行初始化,另外需要創建錯誤處理對象,並和解碼對象進行關聯*/
//創建解碼對象,其是一個結構體變數
struct jpeg_decompress_struct cinfo;
//創建錯誤處理對象
struct jpeg_error_mgr jerr;
//將錯誤處理對象與解碼對象相關聯
cinfo.err = jpeg_std_error(&jerr);
//對解碼對象進行初始化
jpeg_create_decompress(&cinfo);
/*[2]:打開待解碼的jpg圖片,使用fopen的時候需要添加選項”b”,以二進位方式打開文件!*/
FILE * infile; //接收打開文件的文件指針
unsigned char * buffer; //輸出行緩衝區
int row_stride; //buffer一行的像素點數量,即圖片的寬度
// 以二進位方式打開圖片,併進行錯誤處理
if ((infile = fopen(filename, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", filename);
return 0;
}
//把打開的文件的文件指針和解碼對象進行綁定
jpeg_stdio_src(&cinfo, infile);
/*[3]:讀取待解碼圖片的文件頭,並把圖像信息和解碼對象進行關聯,通過解碼對象對jpg圖片進行解碼*/
(void) jpeg_read_header(&cinfo, TRUE);
/*[4]:可以選擇設置解碼參數,如果打算以預設參數對jpg圖片進行解碼,則可以省略該步驟!*/
/* 在該習題要求中,並不涉及圖片縮放等問題,所以我們可以省略該步驟
* jpeg_read_header(),
*/
/*[5]:開始對jpg圖片進行解碼,調用函數之後開始解碼,可以得到圖像寬、圖像高等信息!*/
//我們只需要調用該函數,將圖像信息放入解碼對象中,無需註意其的返回值
(void) jpeg_start_decompress(&cinfo);
/*[6]:開始設計一個迴圈,在迴圈中每次讀取1行的圖像數據,並寫入到LCD中*/
//計算圖像一行的大小
row_stride = cinfo.output_width * cinfo.output_components;
//為自定義緩衝區申請堆記憶體,註意申請的記憶體空間大小應為圖像一行的大小
buffer = calloc(1,row_stride);
//定義一個int類型變數,用於存放顏色分量數據
int data = 0;
/*定義一個迴圈,用於迴圈寫入一行的圖像數據;
使用解碼對象當前掃描行數與圖像的高比較結果作為迴圈條件,當兩者相等,即圖像數據寫入完後退出迴圈*/
while (cinfo.output_scanline < cinfo.output_height)
{
/*調用jpeg_read_scanlines函數,讀取解碼對象中的圖像一行數據,並存放進自定義緩衝區中
且cinfo.output_scanline會隨著調用該函數而增加1,保證while迴圈能夠正常退出*/
(void) jpeg_read_scanlines(&cinfo, &buffer, 1); //從上到下,從左到右 RGB RGB RGB RGB
//將緩衝區中存儲的數據逐一寫入LCD的記憶體映射空間中
for (int i = 0; i < cinfo.output_width; ++i) //012 345
{
/*由於圖片沒有透明度,所以一個像素點大小為3byte,而data為int類型變數,所以需要
藉助"|=" 使得顏色分量順序存儲正確;又因為JEPG存儲顏色分量順序為RGB,所以進行下麵演算法*/
data |= buffer[3*i]<<16; //R
data |= buffer[3*i+1]<<8; //G
data |= buffer[3*i+2]; //B
/*把像素點寫入到LCD的指定位置。其中800*start_y + start_x控制的是用戶自定義的圖片顯示初始位置;
800*(cinfo.output_scanline-1)控制的是寫入圖像數據的行數切換;+ i控制的是寫入圖像數據的列數切換*/
lcd_mp[800*start_y + start_x + 800*(cinfo.output_scanline-1) + i] = data;
//最後需將data內部清零,避免對下一次迴圈的顏色分量寫入造成影響
data = 0;
}
}
/*[7]:在所有的圖像數據都已經解碼完成後,則調用函數完成解碼即可,然後釋放相關資源!(不要遺漏打開的圖像文件)*/
//解碼完成
(void) jpeg_finish_decompress(&cinfo);
//釋放解碼對象
jpeg_destroy_decompress(&cinfo);
//關閉打開的圖像文件
fclose(infile);
return 1;
}
int main(int argc, char const *argv[])
{
//1.打開LCD open
int lcd_fd = open("/dev/fb0",O_RDWR);
//2.對LCD進行記憶體映射 mmap
int *lcd_mp = (int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
//3.讀取圖片初始顯示位置
int start_x = 0, start_y = 0;
printf("Please input start_x :\n");
scanf("%d", &start_x);
printf("Please input start_y :\n");
scanf("%d", &start_y);
//3.顯示一張jpg
read_JPEG_file (argv[1],start_x,start_y,lcd_mp);
return 0;
}