源代码仓库:https://github.com/leelitian/TinyJPEGDecoder
说明:轻量级JPEG解码器,代码的远古版本可以通过Google找到。
项目架构
主要分为以下三个部分,在main函数中对应三个调用函数:
- tinyjpeg_parse_header:解析jpeg文件头
- tinyjpeg_decode:解码jpeg的图像数据
- write_yuv:将解码后的数据写入文件
tinyjpeg_parse_header
这部分的解析,可以参考以下两篇文章,写的很详细:
https://blog.csdn.net/weixin_41926958/article/details/106308576
https://segmentfault.com/a/1190000021169775
tinyjpeg_decode
在看这部分代码之前,我们需要知道在JPEG中图像数据是如何存储的,首先需要了解MCU和DU的概念。
DU是数据单元,一个DU就是DCT变换块,在JPEG中固定为8X8;
MCU是最小编码单元,也被称作宏块,它取决于JPEG图像中Y、U、V三个分量的采样因子的比例。
一般来说,U和V的采样因子为1x1,当Y的采样因子也为1x1时,图像格式称为YUV444,即各个分量都是逐点采样。此时MCU的大小为8x8,即我们将图片分割为许多8x8的MCU进行编码。在一个MCU中包含3个分量,也就是MCU = YDU + UDU + VDU
。
当Y的采样因子为2x2时,也就是Y逐点采样,而U和V在2x2像素上采一次样,图像格式称为YUV420,此时8x8的图像中Y、U分量的大小仅为4x4,不够一个DU的大小,因此我们把MCU设置为16x16。此时MCU = 4 * YDU + UDU + VDU
。
参考1:https://blog.csdn.net/hudaliquan/article/details/38589747
参考2:https://blog.csdn.net/xjhhjx/article/details/80291465
而我们说,JPEG在存储图像数据时,是以一个MCU一个MCU存储的。
以MCU = 4 * YDU + UDU + VDU
为例,我们依次从码流中读入4个YDU、1个UDU、1个VDU,经过反DCT变换、反量化,就能够得到16*16大小宏块的信息。
// MCU: Minimum Coding Unit
xstride_by_mcu = ystride_by_mcu = 8;
// DU都是8x8的
// Y.H x Y.V = 1 x 1,此时 MCU(8x8) = YDU + UDU + VDU,相当于YUV444
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
trace("Use decode 1x1 sampling\n");
// Y.H x Y.V = 1 x 2,不支持
} else if (priv->component_infos[cY].Hfactor == 1) {
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;
trace("Use decode 1x2 sampling (not supported)\n");
// Y.H x Y.V = 2 x 2,此时 MCU(16x16) = 4*YDU + UDU + VDU,相当于YUV420
} else if (priv->component_infos[cY].Vfactor == 2) {
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;
trace("Use decode 2x2 sampling\n");
// Y.H x Y.V = 2 x 1,此时 MCU(16x8) = 2*YDU + UDU + VDU,相当于YUV422
} else {
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
trace("Use decode 2x1 sampling\n");
}
在获得各个DU之后,依据采样方式和输出格式的不同,采取不同的措施,在这里举一个例子:
若JPG文件采样方式为1x1,即文件格式为yuv444,而我们要求的输出格式yuv420p。对于Y分量,我们需要全部写入文件,而对于UV分量,我们需要先进行2x2的采样,才能写入文件。详见:YCrCB_to_YUV420P_1x1函数。
其它
其它注解见仓库源代码,如果博主有说的不对或者疑惑的地方,欢迎评论留言。