BMP格式圖片縮放及在LCD螢幕展示練習

Rice_rice發表於2024-05-12
/**
 * @brief :實現bmp格式圖片的2倍縮小功能,並輸出新的目標bmp格式檔案。最後利用800*480的開發板,展示縮放後的bmp檔案
            因為只是進行函式練習,未採用函式封裝的做法
 * @author ni456xinmie@163.com
 * @date 2024/05/12
 * CopyRight (c)  2023-2024   ni456xinmie@163.com   All Right Reseverd
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <stdlib.h>

#define MUL 2 // 規定縮放倍數,多重倍數需後續筆者繼續最佳化

#pragma pack(1)                    // 取消預設位元組對齊
typedef struct tagBITMAPFILEHEADER // bmp格式圖片的檔案資訊頭1
{
    unsigned short type;      // 檔案標識,字母編碼“BM”
    unsigned int size;        // 點陣圖檔案大小,以位元組為單位
    unsigned short reserved1; // 點陣圖檔案保留字,為0
    unsigned short reserved2; // 點陣圖檔案保留字,為0
    unsigned int offBits;     // 檔案開始到點陣圖資料開始自建的偏移量,單位位元組
} BMF_HEADER;

typedef struct tagBITMAPINFOHEADER // bmp格式圖片的檔案資訊頭2
{
    unsigned int biSize;         // 影像描述資訊塊大小,常為28H
    int width;                   // 影像寬度
    int height;                  // 影像高度
    unsigned short biPlanes;     // 影像plane總數(恆為1)
    unsigned short biBit_depth;  // 記錄顏色的位數取值1
    unsigned int biCompression;  // 資料壓縮方式
    unsigned int biSizeImage;    // 影像區資料的大小,必須是4的倍數
    int biXPelsPerMeter;         // 水平每米畫素
    int biYPelsPerMeter;         // 垂直每米畫素
    unsigned int biClrUsed;      // 影像所用顏色素,不用,固定為0
    unsigned int biClrImportant; // 重要顏色數,不用,固定為0
} BMFI_HEADER;
#pragma pack() // 恢復預設位元組對齊

int main(int argc, char *argv[])
{
    // 1.開啟源圖片檔案,並進行資訊提取
    BMF_HEADER S1;
    BMFI_HEADER S2;                       // 用於標頭檔案資訊提取
    FILE *bmp_fp = fopen(argv[1], "rb+"); // 開啟源圖片檔案
    if (argc != 3)                        // 錯誤判斷
    {
        printf("Usage:%s <srcfile><dstfile>\n", argv[0]);
        exit(1);
    }
    if (!bmp_fp) // 錯誤判斷
    {
        perror("fopen()");
        exit(1);
    }
    fread(&S1, 1, 14, bmp_fp); // 進行源圖片的標頭檔案資訊提取
    fread(&S2, 1, 40, bmp_fp);

    int bmp_size = S2.width * S2.height * S2.biBit_depth / 8;
    char bmp_buf[bmp_size];              // 申請空間,存放源圖片資訊
    fread(bmp_buf, 1, bmp_size, bmp_fp); // 讀取圖片檔案資訊
    fclose(bmp_fp);                      // 關掉源圖片檔案

    // 2.進行源圖片的資料處理
    /*
    此處的思路,是先將圖片資訊進行縮放,即用一個新的陣列進行取樣,每MUL個原陣列拼成一個新的陣列
    然後再利用取樣好的資料進行輸出到檔案或者LCD
    因此此處僅考慮按順序取樣即可,保證資料不丟失
    */
    char newbuf[bmp_size / MUL / MUL]; // 申請陣列,存放重新取樣後的圖片資訊
    int cnt = 0;
    for (int y = 0; y < S2.height; y += MUL) // 存放按倍數縮放取樣後的圖片資訊,每次前進MUL個畫素點
    {
        for (int x = 0; x < S2.width; x += MUL)
        {
            newbuf[cnt++] = bmp_buf[(y * S2.width + x) * 3];     // B
            newbuf[cnt++] = bmp_buf[(y * S2.width + x) * 3 + 1]; // G
            newbuf[cnt++] = bmp_buf[(y * S2.width + x) * 3 + 2]; // R
        }
    }

    // 3.建立新的圖片檔案,並存入相關資訊
    FILE *new_fp = fopen(argv[2], "wb+"); // 建立新的圖片檔案
    if (!new_fp)                          // 錯誤判斷
    {
        perror("fopen()");
        exit(1);
    }
    S2.width = S2.width / MUL;
    S2.height = S2.height / MUL;
    fwrite(&S1, 1, 14, new_fp); // 往新的圖片檔案寫進內容;先寫頭1
    fwrite(&S2, 1, 40, new_fp); // 往新的圖片檔案寫進內容;寫頭2
    fwrite(&newbuf, 1, bmp_size / MUL / MUL, new_fp);
    fclose(new_fp);

    // 4.開啟LCD裝置操作,並申請對映空間
    int lcd_fd = open("/dev/fb0", O_RDWR); // 開啟LCD裝置
    int *lcd_map = (int *)mmap(NULL,
                               800 * 480 * 4, // 申請對映空間,此處一定要是裝置的尺寸
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED,
                               lcd_fd,
                               0);
    // 5.設定圖片位置偏移量,並將圖片資訊寫入對映記憶體
    int X, Y;
    printf("Please enter the location(X Y):\n"); // X和Y是相對於左上角點的位置,讓使用者輸入
    scanf("%d %d", &X, &Y);
    int i = 0;
    int data = 0;
    for (int y = S2.height / MUL - 1; y >= 0; y--) // 此處要寫新圖片的尺寸,以完整的輸入整張圖片資訊
    {
        for (int x = 0; x < S2.width / MUL; ++x)
        {
            // 把BMP圖片的一個畫素點的顏色分量轉換為LCD螢幕的一個畫素點的顏色分量格式  ARGB <--- BGR
            data |= newbuf[i];                     // B
            data |= newbuf[i + 1] << 8;            // G
            data |= newbuf[i + 2] << 16;           // R
            lcd_map[800 * (y + Y) + x + X] = data; // 此處要寫LCD螢幕的高度
            i += 3;
            data = 0;
        }
    }
    close(lcd_fd);
    munmap(lcd_map, 800 * 480 * 4); // 此處要寫LCD螢幕的尺寸
    return 0;
}

相關文章