这篇文章介绍了 JPEG 编码的过程,我们在使用的过程中其实有更高级的功能可以大幅减少计算量, 提高图片的处理速度。这篇文章将针对 JPEG 图片的剪裁和缩放进行讨论。
图片剪裁
我们知道,JPEG 编码时候将图片按照等规格的 MCU 来存放,MCU 是指 8x8 或者 16x16 大小的图片块,所有图片块加起来组成整个图片。此时聪明的你可能 已经想到,可以根据剪裁的需要解码必要的 MCU,其余 MCU 跳过即可。
这个想法可以使用 libjpeg 底层的 API 来实现,参考文档 Partial image decompression。
img_decoded = (JSAMPROW)malloc(sizeof(JSAMPLE) * crop_width * crop_height * dinfo.num_components);
real_left = (JDIMENSION)crop_left;
real_width = (JDIMENSION)crop_width;
// 需要局部解码图片的话,使用 real_left 和 real_width,因为解码必须整个MCU操作,最终的出来的行还需要一次拷贝才完整。
// 调用 jpeg_crop_scanline 这个函数是为了保证实际解码的部分是完整的MCU。这时候 real_left 和 real_width 会被修改。
if (options->crop.left > 0 || crop_width < dinfo.image_width) {
jpeg_crop_scanline(&dinfo, &real_left, &real_width);
}
// 横向跳过指定行数
if (crop_top > 0) {
jpeg_skip_scanlines(&dinfo, (JDIMENSION)crop_top); // 实际上应该校验返回值是否真的都能成功跳过了。
}
// 计算解码每行需要的字节数和实际提取出来有效的字节数
img_row_size = sizeof(JSAMPLE) * real_width * dinfo.num_components;
img_real_row_size = sizeof(JSAMPLE) * crop_width * dinfo.num_components;
// 申请内存,用于接收 scanline 的数据,即比实际大的数据
img_row = (JSAMPROW)malloc(img_row_size);
// real-left 是指实际解码的像素 left 坐标,很可能都在 crop_left 的左面,img_row 接收到的数据会比 crop_left 多,要自己剪裁下忽略掉,
// 这里计算需要忽略的字节数。
img_real_row_start = img_row + (sizeof(JSAMPLE) * (crop_left - real_left) * dinfo.num_components);
current_row = img_decoded; // 指向当前第一行的指针
while (dinfo.output_scanline < crop_top + crop_height) {
// 逐行读取 scanlines,每行结果用 img_row 来接,因为 MCU 只能整个解码,实际 real_width 有可能比 crop_width 大。
// 每次只读一行,每行的前面有 (crop_left - real_left) 个像素被剪裁了。
jpeg_read_scanlines(&dinfo, &img_row, 1);
// 实际上读出来的 scanline 会多于需要的像素,所以复制一下到 img_decoded
memcpy(current_row, img_real_row_start, img_real_row_size);
// 每读完一行,current_row 就移动到下一行,方便后续继续追加数据
current_row += img_row_size;
}