1. 先搞清楚一个基本问题图像格式到底在存什么我接触过不少刚入门的朋友一说起图片格式第一反应就是“后缀名不同而已”。真到项目里跑起来问题就全冒出来了为什么同一张图存成BMP能到几MB存成JPG只有几百KB为什么PNG能透明而JPG不能为什么用Python读一张图的像素值有的返回RGB有的返回BGR这些问题如果不从最底层的“图像格式在存什么”开始理解后面排查起来会非常痛苦。所以这篇系列文章的第一篇我先不急着讲某个工具怎么用而是把BMP、RGB、JPG、PNG这几座地基一次讲透。任何一张位图Bitmap格式的图像在磁盘上存的本质就三样东西宽高尺寸、每个像素的颜色数据、以及如何把这些数据组织起来。你可以把图片格式想象成一种“打包规则”同样一堆像素颜色用不同规则打包体积、清晰度、附加能力比如透明通道都会不一样。关键区别在于“像素颜色”本身和“存储规则”是两回事。RGB是描述颜色的模型BMP、JPG、PNG是存储颜色的容器。很多人分不清这一点才会在“为什么PNG图读出RGB值”这种基础问题上卡住。后面几节我会依次拆解先把这套底层逻辑建立起来你再去处理像“python读取图片rgb值”“bmp头文件解析”这类具体需求就会顺很多。2. RGB数字图像世界的通用语言2.1 像素与色彩的二进制关系数字图像的最小单位是像素Pixel每个像素记录一个颜色。RGB模型就是把一个颜色拆成红Red、绿Green、蓝Blue三个通道每个通道用0到255的整数表示亮度255是当前通道最亮0是最暗。三个通道叠加就能组合出我们肉眼看到的绝大多数颜色。为什么是0到255因为8位二进制能表示256个值2的8次方一个通道占1字节三个通道就是24位色也叫真彩色。一张1920x1080的图像素总数是1920乘1080约207万个每个像素3字节裸数据大约是6MB。这就是“像素位深”概念的开始。很多初学者容易忽略一个细节RGB只是“约定”不规定内存里谁排前面。比如Windows环境下BMP内部往往按BGR顺序存储OpenCV读图默认也是BGR顺序而PNG、JPG解出来通常是RGB顺序。你在用Python的PIL库读取“RGB”图片时得到的确实是(R,G,B)顺序但用OpenCV读取同一张图返回的却是(B,G,R)。这个问题非常容易踩坑后面我会专门演示怎么处理。2.2 常见RGB变体BGR、RGBA、ARGB、BGRA真正接触图像处理之后你会发现RGB前面总是被加字母。最典型的有下面几种变体含义常见场景RGB红绿蓝三通道PNG、JPG解码结果PIL/RGB图像BGR蓝绿红三通道OpenCV默认顺序BMP文件内部常见RGBA红绿蓝加Alpha透明通道PNG带透明通道网页前端CSSARGBAlpha在最前Android Bitmap、某些游戏资源BGRA蓝绿红加AlphaWindows DIB、DirectX纹理常用是不是很乱但乱有乱的原因。历史惯性、内存对齐、硬件优化都在起作用。你现在不需要把每种都背下来只需要记住一个核心规则做图像处理时先确认你手里的像素顺序再动手算颜色。我在实际项目里见过有人用OpenCV读图出来颜色发蓝发红排查半天才发现是RGB和BGR顺序搞反了。2.3 为什么还需要HSV、YCbCr这些非RGB模型RGB适合显示器直接显示但不适合人类感知和压缩编码。人眼对亮度变化敏感对色彩细节相对迟钝。如果我们把图像从RGB转换到YCbCr模型Y代表亮度Cb和Cr代表蓝色差和红色差那么在压缩时就可以对Cb、Cr通道多舍弃一些细节肉眼几乎察觉不到。JPG压缩的第一个步骤就是这个转换。HSV在图像分割和调色工具里更常用它把颜色拆成色相Hue、饱和度Saturation、明度Value方便按颜色范围做选区。“rgb转hsv”也一直是很多图像处理从业者的常用操作。你只要理解RGB只是色彩空间的一种坐标系统不是唯一坐标系统后面的JPG压缩原理、PNG调色板逻辑、甚至硬件屏幕的接口转换比如“3路RGB接口转LVDS”才有解释的基础。3. BMP站在原地的老实人3.1 BMP的本质像素裸数据加一层薄薄的文件头BMPBitmap是微软在Windows时代定义的老牌位图格式也是目前主流格式里最“诚实”的一种。它基本不压缩像素数据虽然内部也支持RLE压缩但现实中极少用文件里面是什么打开屏幕就是什么。你可以把BMP直接理解成“把像素裸数据照单全收”再加上一个描述文件信息的“清单”。这份清单就是常说的BMP头文件。整体结构分四段文件头BITMAPFILEHEADER、信息头BITMAPINFOHEADER、调色板可选、像素数据区。前两段最常用理解它们就够日常使用了。3.2 BMP头文件拆解从bfType到biSizeImage下面我用一张表把BMP文件头最关键的字段列出来字段名使用Windows SDK里的常见定义偏移量字段名大小字节含义0x00bfType2固定值0x4D42ASCII字符为BM用于识别文件是否为BMP0x02bfSize4整个BMP文件大小单位字节0x06bfReserved1/24保留字段通常为00x0AbfOffBits4从文件起始到像素数据区的偏移量0x0EbiSize4信息头大小一般40字节0x12biWidth4图像宽度单位像素0x16biHeight4图像高度单位像素正数表示自底向上存储负数表示自顶向下0x1AbiBitCount2每像素位数常见24或320x1CbiCompression4压缩类型0表示不压缩0x20biSizeImage4像素数据区大小不压缩时可设为00x24biXPelsPerMeter4水平分辨率打印用0x28biYPelsPerMeter4垂直分辨率打印用0x2CbiClrUsed4实际使用的调色板颜色数0x30biClrImportant4重要颜色数通常为0这里有个重要的细节bfOffBits并不是固定的54字节。如果存在调色板像素数据区的起始位置会往后移如果位深是24位或32位一般没有调色板bfOffBits就直接是54。另外biSizeImage在不压缩的情况下理论值等于width * height * (bitCount / 8)但BMP有“每行按4字节对齐”的规则行末会补齐字节后面我会演示怎么算。3.3 实战演练用Python读BMP文件头理解了字段下一步就是动手验证。这是很多人想做的“bmp头文件解析”和“bmp生成”。下面我用Python标准库打开一个BMP文件按偏移量读取关键字段不做任何第三方依赖import struct def read_bmp_header(filepath): with open(filepath, rb) as f: data f.read(54) if data[:2] ! bBM: raise ValueError(这不是一个有效的BMP文件) bf_type data[:2] bf_size struct.unpack(I, data[2:6])[0] bf_off_bits struct.unpack(I, data[10:14])[0] bi_size struct.unpack(I, data[14:18])[0] width struct.unpack(i, data[18:22])[0] height struct.unpack(i, data[22:26])[0] bit_count struct.unpack(H, data[28:30])[0] compression struct.unpack(I, data[30:34])[0] size_image struct.unpack(I, data[34:38])[0] print(f文件类型标记: {bf_type}) print(f文件总大小: {bf_size} 字节) print(f像素数据偏移: {bf_off_bits} 字节) print(f信息头大小: {bi_size} 字节) print(f图像尺寸: {width} x {height}) print(f位深: {bit_count} bit) print(f压缩类型: {compression}) print(f像素数据大小字段: {size_image} 字节) row_size ((bit_count * width 31) // 32) * 4 expected_pixel_size row_size * abs(height) print(f按4字节对齐计算出的实际像素区大小: {expected_pixel_size} 字节) read_bmp_header(example.bmp)注意号前缀代表小端序因为BMP在Windows上默认用小端字节序存储多字节整数。row_size的计算方式就是((bit_count * width 31) // 32) * 4这是BMP格式“每行4字节对齐”的精髓。如果你自己在Windows上用“画图”软件存一张24位BMP然后用这段代码跑一遍会看到bfSize和文件属性里的字节数完全一致expected_pixel_size也和bfSize减去bfOffBits对得上。我第一次手写BMP生成脚本的时候就是在这个行对齐上栽了跟头宽度不是4的倍数时直接顺序写入像素数据会生成一个“看起来能打开但底部颜色错乱”的BMP。后来老老实实按文档补了行尾填充字节才恢复正确显示。这就是为什么我推荐你实际动手读一次文件头单纯背文档记不住踩一次坑就记住了。3.4 现在还有必要用BMP吗很多人的直觉是“BMP这么大早该被淘汰了”。但实际工程里BMP依然有不可替代的位置。嵌入式开发、FPGA图像采集、某些工业检测软件、Windows桌面程序的图标资源至今还大量使用BMP。原因很简单解码零成本不需要解压直接内存映射就能显示格式公开透明不会像某些私有格式那样还要解密。另一个典型场景是“bmp生成”需求比如机器视觉测试时我们需要构造一张特定颜色分布的标准图BMP是最容易手工构造的格式。如果你和单片机或ARM板子打交道还会遇到“3路RGB接口转LVDS”这类硬件连接问题。这里的“RGB接口”一般指并行RGB信号线每路对应一个颜色通道位深可能是16位、18位或24位。软件层把BMP像素数据解析后通过GPIO或专用LCD控制器送到RGB接口再接一颗LVDS桥接芯片转成屏幕能识别的差分信号。在这种硬件链路里BMP因为格式简单、像素排列直观反而是调试时最好用的测试图源。所以结论是BMP不是一个该被当成古董的格式它的价值在于“裸数据和零解码”。当你需要完全掌控图像内存布局时它是最合适的起点。4. JPG有舍才有得的压缩大师4.1 JPG为什么能压到那么小JPGJPEG采用有损压缩核心思路不是简单减少像素而是利用人眼视觉特性“骗”过观察者。JPEG编码流程大致分五步颜色空间转换、色度下采样、分块DCT变换、量化、熵编码。先看颜色空间转换。前面提到RGB转成YCbCr这一步就把亮度和色彩分离。接着是色度下采样也就是对Cb、Cr通道做减半或更低的采样比如常见的4:2:0意思就是每4个亮度像素只保留2个色度像素。人眼对亮度敏感对色度迟钝所以这个操作肉眼几乎察觉不到但文件体积直接减少一半。然后是DCT离散余弦变换把每个8x8像素块从空间域转换到频率域得到64个频率系数。低频系数代表人眼敏感的大面积亮度变化高频系数代表人眼不敏感的细节边缘。接下来量化阶段就是用一个量化表去除掉大量高频系数让它们变成0这一步是有损压缩的主要来源。最后用Zigzag扫描把系数重新排列再做哈夫曼或算术编码把连续出现的0合并起来最终体积就下来了。4.2 为什么JPG越存越糊质量因子怎么选JPG最经典的坑是“二次压缩”。你拿一张JPG不做任何修改直接另存为JPG软件会把它解码成像素再编码回JPG这个过程中量化误差会叠加一次。再存一次误差继续叠加画面会越来越糊。我习惯把它类比成复印机拿复印件再去复印每次都会丢失一点清晰度。所以处理JPG的正确姿势是在编辑过程中始终保留一个无损工作版本比如PNG或PSD最后输出JPG时才转一次。如果只能用JPG也尽量在最高质量的源文件上操作避免反复“解码-编码”循环。质量因子Quality Factor一般是1到100实际使用大多数软件给到80到95。大于95时文件体积暴涨人眼却几乎看不出差别低于60时压缩痕迹会非常明显出现块效应和色带。我自己的经验是摄影作品用90到95网页配图用80到85验证码、小缩略图这类对细节不敏感的可降到60到70。另外要注意“Exif方向信息”问题。很多手机拍出来是横着的JPG内部存了一个Orientation方向标识理论上解码时应该自动旋转但有些工具不认这个字段导致图片方向不对。每次拿到相机导出的JPG我第一件事就是检查Exif避免后续流程被方向问题带偏。4.3 Data URL里的“data:image/jpg;base64,”到底在说什么现在网页开发里经常看到一串以data:image/jpg;base64开头的神秘字符串。它的含义很简单这是Data URL把图片文件本身编码成Base64字符串内嵌到HTML、CSS或JavaScript中减少一次HTTP请求。结构拆解部分含义data:Data URL协议头image/jpgMIME类型声明这是JPEG图片;base64表示后面的数据用Base64编码,分隔符与真正的编码数据隔开/9j/4AAQSkZJRg...Base64编码后的图片二进制内容注意/9j其实是JPEG文件头FF D8 FF E0经过Base64编码之后的结果。现实中很多地方也会写成data:image/jpeg;base64两个都是合法写法因为image/jpg和image/jpeg在实际使用中都被浏览器接受。遇到这个场景我最常做的事是把Base64字符串前几十个字符解码查看文件头判断真实格式。如果你拿到一串Base64数据前面是iVBORw0KGgo那它其实是PNG而不是JPG因为PNG的文件头经过Base64编码后固定以iVBOR开头。这个技巧在排查“为什么我转出来的Data URL无法显示”时特别有用。微信dat文件转JPG也是一个热门需求。微信的dat加密文件会在原文件头部加上异或字节如果你推断出异或值把每个字节异或回去就能还原成JPG。具体做法是读前几个字节和常见图片头FF D8 FF比较算出异或密钥再对整个文件做异或运算。这个操作的前提同样是理解JPG的文件头特征反过来验证你对文件头的掌握程度。4.4 JPG用户注意事项与适用边界JPG不支持的透明通道这是它和PNG最核心的区别。还有JPG不支持图层、不支持动画、不适合线条文字类图像。边缘锐利的UI图标一旦存成JPG边缘会出现灰边或锯齿所以UI设计稿、图标、线条图都别用JPG。JPG只适合照片、渐变丰富的自然图像、网络分享图这类“容错度高”的场景。另外还有一个常见需求把JPG转成PNG或者PNG转JPG。转之前先想清楚JPG转PNG把有损格式放进无损容器体积变大但画质不会提升不能指望这样“修复”照片PNG转JPG如果原图带透明通道转出来的透明区域会被填充成黑色或白色取决于工具设置很多人就是在这里翻车。5. PNG无损压缩与透明通道的平衡派5.1 PNG的滤波与DEFLATE压缩PNGPortable Network Graphics诞生于GIF的专利纠纷时期设计目标是无损压缩、支持透明、比GIF压缩率更高。它的核心算法分两步滤波Filtering和DEFLATE压缩。滤波不是滤镜效果而是“让像素数据更容易压缩”。PNG把每一行像素按滤波类型处理一遍计算当前像素和左边、上边、左上角像素的差值然后存储差值而不是原始值。因为相邻像素往往很接近差值序列里会出现大量0和接近0的小数这对DEFLATE压缩算法非常友好。DEFLATE算法是组合了LZ77滑动窗口和哈夫曼编码的无损压缩算法。LZ77负责把重复出现的字符串替换成引用哈夫曼编码负责把常见符号用短编码表示。靠着这套组合拳PNG在无损前提下能比BMP小很多尤其是大面积的纯色区域体积压缩效果非常明显。“png转dwg”这个需求我见过不少。先说清楚这两个格式是完全不同的数据模型PNG是像素点阵图DWG是AutoCAD的矢量图形文件。除非你用的是专门的光栅转矢量工具比如Vector Magic或者开启CAD的光栅图像附着功能否则直接把PNG后缀改成DWG是无效的。把PNG作为底图插入DWG可以但要变成真正的线条矢量图需要做轮廓追踪和矢量化不是改后缀能搞定的。5.2 Alpha通道与透明是如何实现的PNG支持RGBA四通道第四通道就是Alpha代表透明度。值为0表示完全透明255表示完全不透明中间值表示半透明。浏览器或图像软件在显示PNG时会根据Alpha值把背景混合上来看起来就是“透明”的。“怎么制作bmp通道图”的需求很常见。BMP本身也支持Alpha通道只要把位深设成32位每个像素4字节最后1字节就是Alpha。很多三维建模软件、贴图工具都需要带通道的BMP贴图。你可以用Python的Pillow库完成from PIL import Image # 打开一张带透明的PNG img Image.open(icon_with_alpha.png) # 转换为RGBA模式 img img.convert(RGBA) # 保存为32位BMP这样Alpha通道会保留在文件里 img.save(icon.bmp, formatBMP)Pillow保存BMP时如果图像是RGBA模式会自动写入带Alpha的32位BMP。这里要提醒一下不是所有看图软件都能正确显示32位BMP的Alpha通道有些工具会忽略Alpha直接显示黑色背景。所以做通道图交付之前最好先用支持Alpha预览的工具验证别看到黑底就觉得做错了。5.3 硬件和Web场景里PNG的注意事项PNG在Web前端很常用尤其适合图标和需要透明的UI元素。但在WebGIS里“本地png mbtiles 底图”这个需求是从瓦片地图来的MBTiles是一个SQLite数据库文件里面存着一堆固定尺寸的PNG或JPG瓦片前端按地图缩放级别请求对应瓦片。PNG带透明通道适合做叠加图层如果想要底图实景JPG体积更小。所以到底选PNG还是JPG做瓦片要看你做的是基础底图还是矢量叠加层。嵌入式开发里ESP32、STM32这类MCU性能有限PNG虽然体积小但解码需要额外的内存和时间。有些场景下宁可用BMP直接点对点显示也不想在单片机上跑PNG解码库。如果你坚持在ESP32-S3上显示PNG最好先用图像工具把尺寸缩到屏幕分辨率以内转换成合适位深再考虑解码库的RAM开销。还有人在集成到前端时遇到“failed to resolve import ../assets/grenade (1024x128)[frames8].png”这类报错这通常是构建工具无法找到引用路径下的图片资源文件名和实际路径对不上检查import路径即可和PNG格式本身无关。“visio导出png边距小”的问题也很典型。Visio导出图片时默认会把画布裁剪到内容周围控制边距的方法有三种打开“选项-自定义”里调整打印边距或者在“另存为”窗口选择“导出所有尺寸为图片”再手动设置缩放。还有更简单的方式先导出EMF或SVG矢量格式再用工具裁剪成PNG边距完全可控。6. 四种格式横向对比实战怎么选6.1 一张表说清体积、质量、透明度和兼容性对比项BMPJPGPNG全称BitmapJoint Photographic Experts GroupPortable Network Graphics压缩类型不压缩或RLE有损无损透明通道32位BMP支持不支持支持动画不支持不支持不支持典型场景系统图标、嵌入式LCD、工业图像照片、网页大图、社交分享UI图标、界面切图、带透明素材优点解码快、结构简单、完全保真高压缩比、摄影画质好无损、透明、开源缺点体积巨大多次保存有损、无透明照片场景体积比JPG大GIF我这次不细讲它支持动画且只有256色。需要动画时现在更多人也用WebP和APNG。但作为基础认知GIF的256色限制和PNG-8的调色板模式是同一类东西。6.2 不同项目的选择建议如果你是做Web前端界面图标、Logo、需要透明的素材通通优先PNG能用SVG就用SVG比PNG体积更小。照片类内容用JPG压缩质量80到85足够。WebGIS瓦片里底图用JPG叠加层用PNG。如果是嵌入式MCU项目内存只有几百KBBMP解码压力最小但也最占Flash空间。如果图片资源很大考虑PNG加解码库或者用专门的图像转换工具把真彩色图片转成调色板模式再把色深降到16位或8位体积能降一个量级。如果是图片处理脚本比如“python读取图片rgb值”这类需求我建议只想拿到像素颜色先用PIL读成RGB模式想和OpenCV做视觉处理必须明确返回顺序是BGR脚本里统一转换不要混着用。6.3 用Python快速判定图片的实际格式后缀名是可以骗人的。实际工作中经常遇到“把PNG后缀改成JPG”之后文件打不开的情况。正确做法是先读文件头按magic number判断真实格式文件头Hex真实格式FF D8 FF E0/FF D8 FF E1JPG89 50 4E 47 0D 0A 1A 0APNG42 4DBMP47 49 46 38 39 61GIF52 49 46 46WebP用Python判断文件真实格式只需要几行代码import struct def guess_image_format(filepath): with open(filepath, rb) as f: head f.read(8) if head[:2] b\xff\xd8: return jpg if head[:8] b\x89PNG\r\n\x1a\n: return png if head[:2] bBM: return bmp if head[:4] in (bRIFF,): return webp return unknown print(guess_image_format(myfile.dat))这个脚本关键时刻能救命。很多人收到素材后发现打不开丢给我看我第一件事就是判断真实格式而不是盲目乱转。7. 实战中攒下来的格式坑与处理建议7.1 微信dat文件转成JPG的完整思路前面提过微信安卓版缓存图片会被加密成dat文件。拆解思路如下先打开dat文件读前若干字节JPG文件头固定是FF D8 FFPNG文件头是89 50 4E 47。用第一个字节异或0xFF得到密钥验证第二字节异或后是否为0xD8确认密钥有效后对整个文件逐字节异或写出新文件。def decode_wechat_dat(src_path, dst_path): with open(src_path, rb) as f: data bytearray(f.read()) key data[0] ^ 0xFF for i in range(len(data)): data[i] ^ key with open(dst_path, wb) as f: f.write(data) decode_wechat_dat(cache.dat, recovered.jpg)这个方法原理简单但有个前提微信的dat不一定都是JPG也有可能是其他格式所以转完之后要检查文件头。如果得到的头是FF D8 FF就是JPG如果是89 50 4E 47那就改成.png后缀。7.2 文件后缀和真实格式完全对不上我曾收到一个项目资源包里面图片全是.png后缀但有的文件头实际上是JPG的FF D8 FF有的甚至是WebP。这种情况常见于从聊天工具、缓存目录或第三方平台扒拉下来的素材。如果直接用界面可能显示异常资源管理器可能报错。我的做法是写个脚本批量扫描整个目录按文件头重新判断格式统一修正。7.3 HLS分片里全是.png是不是被坑了有次排查视频播放问题发现一个M3U8点播清单本身没问题但分片链接全是.png后缀播放器直接报错。原因是服务端或CDN把视频分片伪装成了图片扩展名或者内容本身是图片序列而不是标准TS/MP4分片。这种情况下直接按M3U8索引用普通播放器解码很容易失败。虽然M3U8清单语法合法但分片文件实际是PNG图片说明这些分片里承载的不是H.264/H.265裸流很可能是把视频帧编码为PNG图片序列再按TS封装或直接切片。处理思路是先拿一个分片看文件头确认真实编码再去选择对应的解码器或拼接方案不要埋头改后缀。这也再次说明拿到任何文件先看文件头比看后缀可靠得多。7.4 图片体积和尺寸的超严格交付要求有不朋友会遇到“上传1张512x512像素200KB以内的PNG格式直角图标”这类需求。这种约束下最需要注意的是文件体积。我刚接触时使劲压缩质量结果发现PNG是无损压缩质量参数对PNG基本没有意义。正确做法是先把图像尺寸缩放到512x512再根据内容类型选择索引色模式PNG-8减少颜色数量最后再用压缩工具处理。如果图片是纯色块和文字PNG-8通常能降到几十KB。如果必须是32位真彩色带透明通道体积控制在200KB以内就要看画面复杂度了高噪点照片转PNG很容易超限。给直角图标生成时还有一个细节所谓的“直角图标”就是不要圆角遮罩注意alpha通道边缘的硬边是否清晰很多设计师会在这里误加抗锯齿半透明像素在严格审查下可能会不合格。7.5 工具链里的小把戏Vousoir导出PNG与WebGIS瓦片Visio导出PNG边距问题前面说了一部分补充一个我个人常用的办法导出SVG再用命令行工具自动裁剪。SVG是矢量裁剪非常精准。需要PNG再转一次清晰度几乎无损。WebGIS的PNG瓦片同理如果直接切图发现拼接缝隙多半是瓦片之间没有留padding或者切图参数里坐标系不对。PNG无损特性在瓦片拼接时比较友好不会因为反复合成产生画质损失。8. 最后的实操建议和扩展方向这一篇把BMP、RGB、JPG、PNG的基础讲完了但图像格式的世界还远不止这些。我最后给你几个可执行性建议第一平时做图片处理时养成分两步验证的习惯先用文件头确认格式再用工具查看位深和通道信息。这个习惯能帮你排除大量莫名其妙的问题。第二尽量保留一个无损源文件。不管是照片还是UI素材原始素材保留PNG、TIFF或PSDJPG只作为导出和交付格式。这样你可以随时从源文件重新导出任何格式而不是被一次有损压缩锁死。第三动手做两个小项目来巩固理解用Python构造一个最小BMP文件画一条水平渐变色带再把一张PNG解码成RGBA像素数组用程序把Alpha通道单独提取出来存成灰度BMP。这两个小练习做完你对位图格式的理解会超过一大半靠搜索引擎现学现卖的人。后面的系列文章里我会接着展开GIF的调色板原理、WebP/AVIF这些新格式、以及“如何从零写一个图像格式转换器”。这一篇只要把“图像格式在存什么”“RGB是颜色不是格式”“BMP是裸数据、JPG有损、PNG无损透明”这三点内化掉后面的路就会顺很多。