BMP文件格式从零解析:结构、位深、行对齐与通道图实战
1. 为什么现在还要折腾BMP这种老古董格式先说个事儿我这几年做图形相关的东西从嵌入式GUI、游戏引擎的资源管线到无人机的航拍图处理绕来绕去总逃不开BMP。有人可能觉得都202X年了PNG不香吗JPEG不香吗香但BMP有一种不可替代的傻白甜气质——它不压缩、无失真、结构极简甚至你不需要引入任何第三方库用纯C语言写几十行代码就能从零解析和生成一张完整的BMP图像。正因为这种简单到透明的特性BMP至今仍是很多底层系统的首选格式单片机屏幕驱动里LCD直接拿BMP的像素区做显存映射游戏引擎做UI九宫格、做地形高度图、做美术用的通道图也经常直接输出BMP甚至在很多逆向工程和图像处理的教学场景里BMP就是教科书级别的活体标本。这篇文章不打算讲那些人云亦云的格式科普我尽量把我实际踩过的坑、自己推算过的字节对齐规律、以及怎么徒手造一张BMP通道图的完整过程都写出来。如果你正在做图像处理、嵌入式显示、游戏资源工具开发或者只是对图片到底在磁盘里长什么样有好奇心这篇文章应该能让你少走很多弯路。2. BMP文件结构拆解一张图是怎么被解剖的2.1 文件头BITMAPFILEHEADER14字节的身份证BMP文件的开头14个字节是文件头结构体在Windows SDK里叫BITMAPFILEHEADER。之所以叫文件头而不是信息头因为它描述的是这个文件自身的信息跟图像内容无关。偏移大小字段含义0x002字节bfType固定为0x4D42即ASCII字符BM0x024字节bfSize整个文件的大小单位字节0x062字节bfReserved1保留字段必须为00x082字节bfReserved2保留字段必须为00x0A4字节bfOffBits像素数据区相对文件头的偏移这里最值得注意的是bfType。它必须是B和M两个字符即0x42、0x4D这是用来识别文件类型的魔数。很多工具在打开BMP时先读这两个字节如果不是BM直接就拒绝解析。我见过有人用文本编辑器改了图片内容结果把前面这两个字节改没了图片立刻罢工就是这个原因。bfOffBits字段很多人会忽略但在做图像处理时它非常重要。它告诉你真正的像素数据从哪里开始。为什么不是固定的因为BMP可以有调色板Palette调色板在文件头之后、像素数据之前长度可变所以必须用一个偏移量来定位。不过对于最常见的24位无调色板BMP这个值恒为54。2.2 信息头BITMAPINFOHEADER40字节的参数清单紧跟着文件头的是信息头完整结构叫BITMAPINFOHEADER固定40字节。它记录的是图像的元数据包括宽、高、位深、压缩方式、分辨率等。偏移大小字段含义0x0E4字节biSize本结构体大小固定400x124字节biWidth图像宽度像素0x164字节biHeight图像高度像素正数表示自底向上0x1A2字节biPlanes固定为10x1C2字节biBitCount每像素位数1/4/8/16/24/320x1E4字节biCompression压缩方式0表示BI_RGB不压缩0x224字节biSizeImage像素数据大小不压缩时可填00x264字节biXPelsPerMeter水平分辨率像素/米0x2A4字节biYPelsPerMeter垂直分辨率像素/米0x2E4字节biClrUsed使用的颜色数0表示按位深自动推算0x324字节biClrImportant重要颜色数通常为0有两个字段要特别拿出来说。第一是biHeight的正负号问题。如果这个值是正数说明像素数据在文件里是自底向上存储的也就是文件里先存的是图像的最后一行如果是负数则是自顶向下文件里先存第一行。这个细节极其阴险很多半吊子代码读出来的图是上下颠倒的根本原因就在这里。我后面会用一整节讲清楚。第二是biSizeImage。对于不压缩的BMP这个字段可以填0很多SDK自己会根据宽高和位深重新推算。但如果是用了RLE压缩biCompression为1或2这个字段就必须精确填写。写代码时我建议还是老老实实把真实大小填进去别偷懒为什么因为有些严格的解析器会拿这个值做边界检查填错直接崩。2.3 调色板索引色是怎么玩的调色板只存在于低色深的BMP里也就是1位、4位、8位这三种。它是一张颜色查找表每个表项占用4字节依次是B、G、R三个通道和一个保留字节通常为0。像素数据区里存的并不是真实的颜色值而是调色板的索引号。举个例子一张8位灰度BMP它的调色板通常是一个256项的表第0项是黑色0,0,0第255项是白色255,255,255中间渐变。像素数据里的值比如128表示调色板第128项的颜色。这样做的好处是极大节省空间一个像素只占1字节却能表达256种颜色代价是颜色种类被锁死在调色板里。这里有个坑调色板里的颜色排列顺序不一定是灰度渐变它完全可以乱序。如果你把第128项定义成红色那图上所有值为128的地方就是红色。很多图像处理库在加载8位BMP后想当然地当成灰度图处理结果常见到颜色诡异的图本质就是没考虑调色板的影响。对于24位和32位BMP没有调色板像素直接存放真实颜色值所以bfOffBits固定为144054。2.4 像素数据区真正干活的区域像素数据区是整个文件里最大的一块也是我们做图像处理时真正操作的地方。它的排列规则遵循三个基本原则从左到右是像素的列方向从下到上或从上到下是行方向取决于biHeight正负每行数据需要按4字节对齐。按4字节对齐这个规则是BMP格式和PNG、JPEG最大的区别之一。它要求每一行的像素数据总字节数必须是4的倍数如果不是就在行尾补0。这是为了早期硬件在读取数据时能按32位总线一次读出不需要做跨行拼接。这个规则我单独开一节讲因为它几乎是我见过的所有BMP生成代码出错的重灾区。3. 像素数据存储的核心规则3.1 行对齐Padding最容易翻车的4字节陷阱我说的这个坑几乎所有从零手写BMP的人都会踩一次。BMP规定每行数据的字节数必须是4的倍数计算公式是rowStride ((width * bitsPerPixel 31) / 32) * 4也就是width * bitsPerPixel除以32向上取整再乘以4。实际写代码时更常见的写法是int rowBytes (width * bpp 7) / 8; // 每行实际有效字节数 int stride (rowBytes 3) ~3; // 对齐到4的倍数 int padding stride - rowBytes; // 需要补的字节数假设一张宽度为5像素的24位BMP每像素3字节那么一行有效数据是15字节。因为15不是4的倍数所以要补1字节的0实际每行占16字节。也就是说你写文件的时候第5个像素写完RGB三个字节后还得补一个0x00才能开始下一行。如果你忘了补这个字节后面所有行的数据都会错位读出来的图会呈现一种斜着撕裂的花屏效果行数越多病情越严重。4位和1位的情况更隐蔽。比如4位图一个字节能装两个像素如果宽度是奇数个像素单行有效字节数是ceil(width/2)同样需要补0到4的倍数。我建议不管什么位深统一用上面那个公式算stride别手算一算就容易错。3.2 自底向上还是自顶向下正负高度的大坑这一节值得单独拎出来说。标准BMP格式源于Windows 3.0时代的图形设备接口设计当时的做法是让位图的行顺序跟屏幕扫描线一致但坐标原点在左下角所以像素数据默认从最后一行开始存储这就是自底向上的由来。写代码时如果你用程序生成BMPbiHeight设为正数那么文件里第一行数据对应图像的最底部。很多人在内存里构建图像时习惯从上往下逐行处理直接把内存数据按顺序写入文件结果读出来的图是倒立的。解决方案有两种生成文件时把写行的顺序反过来从最后一行开始写。把biHeight设为负数表示自顶向下存储这样数据顺序就和常规的从上到下一致了。我个人的建议是能不用负高度就尽量不用。虽然几乎所有解析器都支持负高度但少数老旧的嵌入式库只认正高度而且正高度在Photoshop里打开也不会出问题。写代码时养成习惯先算好stride然后按最后一行先写的顺序输出像素数据。3.3 颜色通道排列BGR才是正统这是另一个让新手摸不着头脑的地方。BMP像素数据里的颜色顺序是B、G、R不是我们熟悉的R、G、B。比如一个纯红色像素RGB值应该是(255,0,0)但在BMP文件里写入的是0x00 0x00 0xFF。这个反直觉的设计同样有历史原因可以追溯到显示器硬件早期的信号处理方式但咱们做工程的不需要纠结历史只需要记住一句话凡是BMP写像素时一律BGR。如果你用Python的struct.pack或者C语言的fwrite直接写入像素颜色很容易习惯性地按R、G、B顺序写结果整张图的红蓝通道完全互换人物肤色变成诡异的蓝紫色。排查这种问题时第一反应就是检查通道顺序。32位BMP稍微特殊一点它包含4个通道——B、G、R、AA是Alpha透明度。注意Alpha放在最后一位同样不是ARGB而是BGRA。有的库叫法不同有的叫ABGR本质上都是指同一个顺序不要被花里胡哨的命名搞晕。4. 不同位深的存储差异4.1 1位和4位调色板索引的世界1位BMP每个像素只占1比特只有0和1两个值对应调色板的第0项和第1项通常就是黑色和白色。这类图常被用来存储单色logo、文字掩码、二维码等。它的存储方式是按位打包第一个像素占字节的最高位bit 7依次往下排一个字节可以容纳8个像素。如果一行像素数是3那有效字节是(37)/8 1字节但这1字节里只有前3个bit是有效数据剩下的5个bit要置0。4位BMP类似每像素占4bit一个字节两个像素第一个像素占高4位第二个像素占低4位。宽度5像素的一行有效数据是3字节5个像素分开放入3个字节最后一个字节只用了一半再按4字节对齐就变成了4字节。这两种低色深格式日常图像处理中用得不多但一旦你碰到老式图标、单片机小屏、某些工业设备的固件图片就必须理解这种位打包的规则。核心公式位图宽度W对应的有效字节数 (W * 每像素位数 7) / 8注意这里用的是向上取整然后用这个值再去凑4字节对齐。4.2 8位灰度通道图最强工具8位BMP是我做通道图时最常用的格式。一个像素1字节总共256级灰度非常适合存放高度图Height Map、法线图Normal Map、粗糙度图Roughness Map、环境光遮蔽AO等单通道数据。8位BMP有两种存在形式带调色板的索引图像素值是调色板索引调色板定义256种颜色。如果调色板是黑到白的渐变视觉上就是灰度图。灰度图严格来说BMP没有专门的灰度模式但很多库比如MATLAB、OpenCV在读取8位BMP且调色板为标准灰度渐变时会直接当作灰度数组返回每个像素值直接就是灰度值。在程序里做通道图时我一般不依赖调色板而是直接把灰度值当成像素值写入8位像素区。只要确保调色板是标准的i,i,i渐变读出来的数据就是直观的灰度矩阵。这种库能直接读成灰度数组的好处让它成为很多图像处理算法验证时的首选格式。用Python的PIL读8位BMP你会得到L模式的图像对象直接转成numpy数组就能做数值运算非常顺手from PIL import Image import numpy as np img Image.open(height_map.bmp).convert(L) arr np.array(img) # 二维数组每个值0~255 print(arr.shape, arr.dtype)4.3 24位真彩色最通用的全彩方案24位BMP是最普及的BMP变体每个像素3字节BGR排列。一行的stride计算公式前面已经说了这里重点强调一下它在磁盘上的实际体积——不压缩意味着文件大小等于像素字节数加上54字节的头部无调色板时。举个例子一张1920x1080的24位BMP像素区大小为1920x1080x36,220,800字节约5.93MB文件总大小就是6,220,854字节。对比同尺寸的JPEG可能只有200-500KBBMP的大方可见一斑。这也解释了为什么BMP不适合网络传输和照片存储但在内存映射、帧缓冲、无损处理的场景下反而干脆利落。24位BMP读取时得到的是一串连续的BGR字节流。用C/C做处理时最常见的模型就是申请一个width * height * 3的缓冲区然后一行一行拷贝。注意内存中的行宽度要和文件中的stride保持一致不然越界。4.4 32位带AlphaPNG的老前辈32位BMP每个像素4字节BGRA顺序相比24位多了透明度通道。它在游戏和GUI领域有特殊地位因为很多引擎的纹理格式直接就是BGRA8和32位BMP的内存布局完全一致加载时几乎不需要转换甚至可以memcpy直接塞进GPU纹理。一个常见的场景是抠图美术在Photoshop里把图存成32位BMPAlpha通道保留了透明信息。程序读取时把BGRA数据直接上传给图形APIOpenGL用GL_BGRA_EXTDirectX用DXGI_FORMAT_B8G8R8A8_UNORM纹理自动带透明省去了解码PNG又要转格式的工序。这里有个细节值得记32位BMP因为是4字节/像素天然对齐不存在行补齐问题stride就是width * 4。所以32位BMP反而是几种位深里结构最省心的。5. 手写BMP从零生成一张24位图像5.1 计算文件大小和头部参数徒手写BMP是最能验证你对格式理解的方式。我以生成一张3x2的24位BMP为例把每一步的参数和十六进制数据都列出来。宽度3高度2每像素3字节每行有效字节数3 x 3 9字节对齐后stride(9 3) ~3 12字节每行补齐字节数12 - 9 3字节像素数据总大小12 x 2 24字节文件总大小14文件头 40信息头 24 78字节信息头参数biSize 40biWidth 3biHeight 2正数自底向上biPlanes 1biBitCount 24biCompression 0biSizeImage 24bfOffBits 545.2 用C语言实现写入#include stdio.h #include stdint.h #include string.h #pragma pack(push, 1) typedef struct { uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; } BITMAPFILEHEADER; typedef struct { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; } BITMAPINFOHEADER; #pragma pack(pop) void write_bmp_24(const char* filename, int w, int h, const uint8_t* pixels /* BGR格式自底向上 */) { int rowBytes w * 3; int stride (rowBytes 3) ~3; int padding stride - rowBytes; int pixelDataSize stride * h; BITMAPFILEHEADER fh {0}; BITMAPINFOHEADER ih {0}; fh.bfType 0x4D42; // BM fh.bfSize 54 pixelDataSize; fh.bfOffBits 54; ih.biSize 40; ih.biWidth w; ih.biHeight h; ih.biPlanes 1; ih.biBitCount 24; ih.biCompression 0; ih.biSizeImage pixelDataSize; ih.biXPelsPerMeter 2835; // 约72 DPI ih.biYPelsPerMeter 2835; FILE* fp fopen(filename, wb); if (!fp) return; fwrite(fh, 1, sizeof(fh), fp); fwrite(ih, 1, sizeof(ih), fp); uint8_t zero[3] {0, 0, 0}; for (int y 0; y h; y) { fwrite(pixels (size_t)y * rowBytes, 1, rowBytes, fp); fwrite(zero, 1, padding, fp); // 补位 } fclose(fp); }注意设了#pragma pack(push, 1)强制结构体1字节对齐。如果不设置编译器默认会做内存对齐结构体里会出现空洞写入文件的头部数据就全乱了。这是我见过新手写BMP生成程序时最经典的低级错误——结构体定义完全正确但忘了关对齐生成的头部尺寸不对任何图片查看器都打不开。5.3 构造3x2的示例像素假设我们想要这样的图像自底向上存储所以先写底行底行像素(0,0)红色(255,0,0)像素(1,0)绿色(0,255,0)像素(2,0)蓝色(0,0,255)顶行像素(0,1)白色(255,255,255)像素(1,1)黑色(0,0,0)像素(2,1)灰色(128,128,128)按BGR顺序写入底行就是FF 00 00 | 00 FF 00 | 00 00 FF | 补3字节 00 00 00顶行FF FF FF | 00 00 00 | 80 80 80 | 补3字节 00 00 00文件里像素区的字节顺序是先顶行还是先底行根据自底向上规则文件里先写底行再写顶行。上面C代码的循环里y0对应的是图像最底部的行这一点务必在注释里写清楚不然两个月后你自己回来看代码都会犯迷糊。用十六进制查看器看完整的78字节文件前54字节是标准头部从偏移54开始就是上面这些像素数据。你也可以把这段数据复制到任何支持hex编辑的工具里自己拼一个BMP出来当作练习拼完能正常打开的那一瞬间你对BMP的理解就算是真正过关了。6. 实战怎么制作BMP通道图6.1 什么是通道图为什么用BMP在游戏美术和图形学工作流里通道图通常指的是把不同的材质属性或遮罩信息分别存放在图像的不同颜色通道中。比如一张24位的通道图R通道存金属度MetallicG通道存粗糙度RoughnessB通道存环境光遮蔽AO。引擎采样这张贴图后从albedo.r、albedo.g、albedo.b分别读取三个属性一次采样就拿到了三类数据效率很高。为什么这种场景下很多人选BMP因为BMP无压缩采样时不需要解码而且可以精确控制每个通道的字节内容不存在JPEG的DCT压缩带来的模糊和色偏也不存在PNG压缩带来的潜在失真虽然PNG是无损的但解码开销比BMP大。做离线资源管线时BMP就是最佳的中间格式。还一种常见的通道图是UI界面里的九宫格切图或者是alpha通道图——一张8位灰度图黑色表示完全透明白色表示完全不透明中间灰度表示半透明。这种图在Android和iOS的图标处理、游戏粒子贴图里都经常出现。6.2 用Python快速生成8位通道图假设我们要生成一张256x256的径向渐变通道图中间白、边缘黑用来做角色特效的软边遮罩。用Python只需要十几行import numpy as np from PIL import Image size 256 y, x np.mgrid[0:size, 0:size] cx cy (size - 1) / 2.0 dist np.sqrt((x - cx) ** 2 (y - cy) ** 2) / (size / 2.0) dist np.clip(dist, 0, 1) # 0~255的灰度值中间是1.0(白)边缘是0.0(黑) gray (1.0 - dist * dist) * 255.0 gray gray.astype(np.uint8) img Image.fromarray(gray, modeL) img.save(soft_mask.bmp)这里存成BMP后打开你会得到一张完美的径向渐变灰度图。但如果你用代码读取它有个细节需要注意PIL读8位BMP默认返回L模式但底层很多实现会把调色板信息丢掉。如果后续要把这张图交给别的程序处理最好事先确认对方是否支持调色板型BMP。最稳妥的方法是保存成24位BMP灰度值在三个通道里重复一份虽然占用空间大三倍但兼容性最好。6.3 制作带透明信息的32位通道图再来一个更贴近实际需求的例子。假设美术给了一张角色立绘要做成游戏里的半透明特效需要为它单独制作一张Alpha通道图。做法是读取原图的RGBA信息把透明度单独抽取出来再写回一张32位BMPfrom PIL import Image import numpy as np img Image.open(character.png).convert(RGBA) arr np.array(img) rgb_part arr[:, :, :3] # 颜色保留 alpha arr[:, :, 3:4] # 透明度单独拆出来 bgra np.concatenate([rgb_part[:, :, 2:3], # B rgb_part[:, :, 1:2], # G rgb_part[:, :, 0:1], # R alpha], axis2) # A out Image.fromarray(bgra, modeRGBA) out.save(character_with_alpha.bmp)注意这里我手动把RGB通道换成了BGRA顺序。如果你不做这一步直接用PIL把RGBA数组存成BMP格式某些版本的PIL在内部转换时其实会帮你调顺序但生成的文件在不同查看器里可能会表现出不一样的通道解读。最保险的方式是像上面这样自己控制通道布局输出的32位BMP传到游戏引擎里得到的就是和内存布局完全一致的BGRA纹理。6.4 手写工具时的三个原则做了几年资源管线工具我自己总结出三条做通道图的铁律第一明确位深。8位适合单通道灰度数据24位适合RGB三通道遮罩/属性图32位适合需要Alpha或RGBA四通道的场景。别为了省空间把多通道信息硬塞进8位图里读取端一旦按错误方式解析排查成本远超那一点磁盘空间。第二统一坐标系。很多工具默认图像第一行是顶部但BMP默认自底向上。你在内存里处理时如果用第一行为顶部的逻辑写出文件时一定要选择biHeight -h来保持自顶向下或者主动翻转行的顺序。我倾向于在生成端直接用负高度而在读取端统一转成自顶向下这样团队里其他人不会莫名其妙踩到倒图问题。第三永远自己算stride。哪怕是24位、32位这些看起来简单的格式也建议统一走公式计算。为啥因为当你后期把工具扩展成支持4位或1位BMP时同一套逻辑就不会因为漏了这个环节而出bug。我见过太多工具只支持24位后来需求改成支持8位索引图代码里各种硬编码的地方冒出一堆花屏和错位。7. 常见问题与排查技巧实录7.1 图片上下颠倒症状程序生成或读取的BMP在查看器里是倒的。原因99%是biHeight正负和写入行的顺序不匹配。排查方法先用十六进制工具看一眼文件头确认biHeight的正负再看第一行像素数据到底是图像底部还是顶部。修复一把梭如果生成端是自顶向下写入直接把biHeight取负或者在写入循环里把行顺序倒过来。7.2 图片斜向撕裂、信息错位症状图像内容大致能看出来但整体像被斜着切了一刀每隔几行就错位。这是行补齐padding没做对的特征。常见的错误是24位图漏了补0或者8位图宽度不是2的幂时算错了stride。修复方法就是回到第3.1节用公式严格算一遍stride并在写入时补0。检查时可以在文件的像素区用十六进制工具对比每行的末尾看是否有多余的0字节。7.3 颜色红蓝互换症状原本红色的物体在图上显示为蓝色蓝天变成了橙红色人物肤色发紫。这是把RGB和BGR搞反了。修复查看你的写入代码确保按B、G、R顺序写入或者读取后按BGR转RGB。我习惯在写文件的地方加一个明确的注释// BMP像素顺序是BGR不是RGB否则下次重构代码时很容易又踩一次。7.4 文件能打开但整个是黑的症状文件合法查看器能打开但整张图全黑。这通常有三种可能调色板全为0。8位图如果调色板数据没写或者全写0那么所有索引都映射到黑色。像素数据区域偏移错了。bfOffBits指向的位置不是实际像素数据读出来的是空白或调色板区域的0值。位深不匹配。你写的是24位数据但头部声明的是1位查看器只取了每个像素的第一个bit当然全黑。排查顺序建议先看bfOffBits再看biBitCount最后检查调色板。7.5 文件大小和理论值不一致症状代码生成的BMPbfSize写的是100KB但实际文件只有90KB或者查看器报告文件损坏。这种问题的根源通常是结构体内存对齐即#pragma pack没有设置。用sizeof(BITMAPFILEHEADER)打印一下如果大于14说明结构体有填充字节必须加上1字节对齐的pragma。这是C/C写BMP最容易犯的低级错误没有之一。7.6 不同平台显示不一致同一个BMP文件Windows图片查看器显示正常但在某个开源库里读取时出现尺寸偏差。原因大概率是解析时忽略了biClrUsed或调色板的不同处理逻辑。有些平台在8位图没填调色板信息时会自动补一个灰度渐变调色板有些平台则强制要求调色板存在否则读出来全是黑的。跨平台交换BMP时我个人的建议是尽量使用24位或32位BMP这两种格式无调色板、无位打包是所有解析器公认最不容易出分歧的标准形态。7.7 排查工具推荐工欲善其事必先利其器。我平时排查BMP问题用三件套010 Editor带BMP模板能结构化解码文件头、信息头、调色板一眼看出哪个字段不对劲。HxD轻量级十六进制查看器快速查看和修补字节。ImageMagick的identify命令identify -verbose image.bmp能打印出几乎所有格式信息包括stride、数据偏移、颜色类型比肉眼盯头文件高效得多。identify -verbose test.bmp | head -50如果这三样还不解决我最后的办法是写一个十行的小脚本把BMP头字段逐个打印出来对照格式文档手推。这个过程虽然慢但往往能发现一些被工具自动纠正掉的问题。记住一个原则BMP格式太简单了凡是工具能自动纠错的函数库通常能装看不见凡是报错的基本就是文件本身真的错了。8. 一些踩坑后的心得体会写到这里关于BMP的存储机制、位深差异、通道图制作和问题排查我都讲得差不多了。最后说一点我在实际项目里的体会。BMP这个格式表面上看是老掉牙的技术但它的价值恰恰在于简单到可以完全掌控。每当我需要对图片数据做底层操作时比如做像素级特效、做纹理通道打包、做嵌入式显示的帧缓冲我都会下意识地选择BMP作为中间载体。因为我知道它的每一字节是什么含义我可以预测文件的大小我可以手工验证它的内容。这种确定性是JPEG和PNG给不了的。如果你打算深入做图像或者游戏渲染相关的工作我强烈建议你抽一个下午亲手写一遍生成和解析BMP的工具包括至少两个位深不依赖任何库。这个过程会强迫你把字节对齐、坐标系统、通道顺序这些底子打牢。打好这个底子后续不管碰到DDS、KTX还是ASTC压缩纹理理解起来都会快得多。再分享一个小技巧测试BMP解析器时别用Photoshop生成的图当测试样本因为PS的输出有时候会包含一些非常规字段比如自定义的ICC配置块干扰你对标准格式的判断。正确的做法是用自己的代码生成一张最小的BMP比如1x1像素的24位图总共57字节然后逐步加大尺寸观察不同宽度下行补齐的变化。这种最简复现的思路不只是在BMP上几乎所有格式处理的问题排查都通用。行了就聊到这里。如果你在自己做BMP相关工具时遇到了什么奇怪的坑欢迎在评论区留言交流。说不定你遇到的那个问题我当年也栽过。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →