網路上很多人讀寫 BMP 都是直接呼叫 API 或 Library ,並不懂其中的原理,而且跨平台也麻煩…
所以我這篇文章是自己手動寫 BMP 解碼器,讓大家清楚其中的奧妙。
另外如果你對 BMP 編碼器有興趣,你可以看我寫的這篇文章
https://blog.csdn.net/weixin_38884324/article/details/80612470
讓我們先看執行結果吧,左邊是顯示 BMP 的標頭資訊,右邊則是年輕的身體…,阿不對,是讀取BMP之後顯示的結果啦哈
執行結果 :
C # :
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class NewBehaviourScript1 : MonoBehaviour
{
public RawImage outputImg;
byte[] file = null;
void Start()
{
file = File.ReadAllBytes("C:/A/0.bmp");
tagBITMAPFILEHEADER file_header = new tagBITMAPFILEHEADER();
file_header.bfType = System.Text.Encoding.Default.GetString(file, 0, 2);
file_header.bfSize = _4ByteToInt(0x0002);
file_header.bfReserved1 = _2ByteToInt(0x0006);
file_header.bfReserved2 = _2ByteToInt(0x0008);
file_header.bfOffBits = _4ByteToInt(0x000A);
tagBMP_INFOHEADER info_header = new tagBMP_INFOHEADER();
info_header.biSize = _4ByteToInt(0x000E);
info_header.biWidth = _4ByteToInt(0x0012);
info_header.biHeight = _4ByteToInt(0x0016);
info_header.biPlanes = _2ByteToInt(0x001A);
info_header.biBitCount = _2ByteToInt(0x001C);
info_header.biCompression = _4ByteToInt(0x001E);
info_header.biSizeImage = _4ByteToInt(0x0022);
info_header.biXPelsPerMeter = _4ByteToInt(0x0026);
info_header.biYPelsPerMeter = _4ByteToInt(0x002A);
info_header.biClrUsed = _4ByteToInt(0x002E);
info_header.biClrImportant = _4ByteToInt(0x0032);
print("--------------------------------------------- File Header");
print("Type : " + file_header.bfType);
print("Size : " + file_header.bfSize);
print("Reserved1 : " + file_header.bfReserved1);
print("Reserved2 : " + file_header.bfReserved2);
print("OffBits : " + file_header.bfOffBits);
print("--------------------------------------------- Info Header");
print("BitmapHeaderSize : " + info_header.biSize);
print("Width : " + info_header.biWidth);
print("Height : " + info_header.biHeight);
print("Planes : " + info_header.biPlanes);
print("BitCount : " + info_header.biBitCount);
print("Compression : " + info_header.biCompression);
print("SizeImage : " + info_header.biSizeImage);
print("XPelsPerMeter : " + info_header.biXPelsPerMeter);
print("YPelsPerMeter : " + info_header.biYPelsPerMeter);
print("ClrUsed : " + info_header.biClrUsed);
print("ClrImportant : " + info_header.biClrImportant);
// --------------------------------------------------------
if (info_header.biBitCount != 24) {
Debug.LogError("本範例只能讀取 24 bit 寬度的圖像。");
return;
}
Texture2D t = new Texture2D(info_header.biWidth, info_header.biHeight);
// skip : 微軟規定圖片寬度 / 4 必須 等於 0
// 如果寬度無法整除 4 ,那麼必須要補 0
int skip = 0;
if (t.width * 3 % 4 != 0)
{
skip = 4 - (t.width * 3 % 4);
}
int i = 0;
for (int y = 0; y < t.height; y++)
{
for (int x = 0; x < t.width; x++)
{
int k = file_header.bfOffBits + i;
t.SetPixel(x, y, new Color(file[k + 2] / 255.0f, file[k + 1] / 255.0f, file[k] / 255.0f, 1));
i += 3;
}
i += skip;
}
t.Apply();
outputImg.rectTransform.sizeDelta = new Vector2(t.width, t.height);
outputImg.texture = t;
}
int _2ByteToInt(int offset)
{
int value1 = file[offset + 1] << 8;
int value2 = file[offset];
return value1 | value2;
}
int _4ByteToInt(int offset)
{
int value1 = file[offset + 3] << 24;
int value2 = file[offset + 2] << 16;
int value3 = file[offset + 1] << 8;
int value4 = file[offset];
return value1 | value2 | value3 | value4;
}
public struct tagBITMAPFILEHEADER
{
public string bfType; //2Bytes,必须为"BM",即0x424D 才是Windows位图文件
public int bfSize; //4Bytes,整个BMP文件的大小
public int bfReserved1; //2Bytes,保留,为0
public int bfReserved2; //2Bytes,保留,为0
public int bfOffBits; //4Bytes,文件起始位置到图像像素数据的字节偏移量
}
public struct tagBMP_INFOHEADER
{
public int biSize; //4Bytes,INFOHEADER结构体大小,存在其他版本I NFOHEADER,用作区分
public int biWidth; //4Bytes,图像宽度(以像素为单位)
public int biHeight; //4Bytes,图像高度,+:图像存储顺序为Bottom2Top,-:Top2Bottom
public int biPlanes; //2Bytes,图像数据平面,BMP存储RGB数据,因此总为1
public int biBitCount; //2Bytes,图像像素位数
public int biCompression; //4Bytes,0:不压缩,1:RLE8,2:RLE4
public int biSizeImage; //4Bytes,4字节对齐的图像数据大小
public int biXPelsPerMeter; //4 Bytes,用象素/米表示的水平分辨率
public int biYPelsPerMeter; //4 Bytes,用象素/米表示的垂直分辨率
public int biClrUsed; //4 Bytes,实际使用的调色板索引数,0:使用所有的调色板索引
public int biClrImportant; //4 Bytes,重要的调色板索引数,0:所有的调色板索引都重要
}
// 1,4,8位图像才会使用调色板数据,16,24,32位图像不需要调色板数据,即调色板最多只需要256项(索引0 - 255)。
// 本範例將不會用到此結構體。
public struct tagRGBQUAD
{
public byte rgbBlue; //指定蓝色强度
public byte rgbGreen; //指定绿色强度
public byte rgbRed; //指定红色强度
public byte rgbReserved; //保留,设置为0
}
}
參考 :
微軟官方文檔:https://msdn.microsoft.com/library/windows/desktop/dd183376
中文說明1 https://www.cnblogs.com/l2rf/p/5643352.html
中文說明2 https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html