如何从JPEG格式Bitmap中获取压缩质量值?
我尝试从加载自JPEG图片的Bitmap中获取质量值,编写了如下C#代码:
int GetQuality(Bitmap bitmap) { if (!bitmap.RawFormat.Equals(ImageFormat.Jpeg)) return -1; ImageCodecInfo jpegEncoder = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid); var jpegGuid = jpegEncoder.Clsid; var jpegParams = bitmap.GetEncoderParameterList(jpegGuid); EncoderParameter qualityParam = jpegParams?.Param[1]!; BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; FieldInfo? field = typeof(EncoderParameter).GetField("_parameterValue", bindFlags); object obj = field!.GetValue(qualityParam)!; IntPtr qualityParamValue = (IntPtr) obj; unsafe { //byte* qpvPtr = (byte*) qualityParamValue; //var quality = *qpvPtr; // val is type byte with a value of 0 var quality = *(byte*) qualityParamValue; // val is type byte with a value of 0 return quality; } }
但对于以50%压缩质量(使用EncoderParameter(Encoder.Quality, 50L))保存的JPEG Bitmap,获取到的质量值为0。要么是我的反射及unsafe代码存在问题,要么是JPEG Bitmap中并未存储质量值。我参考EncoderParameter的构造函数编写了上述代码,该构造函数如下:
public EncoderParameter(Encoder encoder, byte value) { _parameterGuid = encoder.Guid; _parameterValueType = EncoderParameterValueType.ValueTypeByte; _numberOfValues = 1; _parameterValue = Marshal.AllocHGlobal(sizeof(byte)); *(byte*)_parameterValue = value; GC.KeepAlive(this); }
其中byte value为0-100的质量值。我的unsafe代码是否正确实现了*(byte*)_parameterValue = value;的逆操作?如果没有,问题出在哪?如果正确,是否意味着压缩后的Bitmap中并未存储质量值?
1. Unsafe代码逻辑无问题
从构造函数的实现来看,_parameterValue指向的是一块分配的字节内存,里面存储了byte类型的质量值。你的unsafe代码*(byte*) qualityParamValue确实是构造函数中*(byte*)_parameterValue = value;的逆操作,这部分逻辑是正确的。
2. 核心原因:JPEG不存储压缩质量参数
你获取到0的本质原因是:加载到内存的Bitmap对象,以及原始JPEG文件本身,都不会存储编码时设置的压缩质量值。
- 压缩质量是JPEG编码时的控制参数,它只影响压缩算法的行为(比如量化表的选择),但这个参数本身不会被写入最终的JPEG文件。
- 当你从JPEG文件加载Bitmap时,.NET仅解码像素数据,不会保留编码时的质量参数;
GetEncoderParameterList返回的是编码器的默认参数或空值,并非原始编码时的设置。
3. 验证方式
直接解析JPEG文件的二进制内容就能确认:JPEG的文件结构(SOI、DQT、SOF等标记)中没有专门存储压缩质量的字段,质量仅通过量化表间接体现,但无法直接还原出原始的0-100数值。
4. 替代方案
如果需要保留压缩质量信息,只能在保存JPEG时,将质量值额外存储到其他位置(比如图片的EXIF元数据、单独的配置文件等),后续读取时再从这些地方获取。
内容的提问来源于stack exchange,提问作者dei

