JPEG compression/decompression using libjpeg-turbo (TurboJPEG API and classic jpeglib API). Invoke when compressing RGB/RGBA buffers to JPEG, decompressing JPEG files/memory to pixels, querying JPEG header info, setting quality/subsampling, handling pixel formats (TJPF_*), error handling with tj3GetErrorStr, or integrating with the engine's jpeg IImageLoader. Also covers lossless transforms, YUV encode/decode, and scaling.
本项目使用 libjpeg-turbo(TurboJPEG 3.x API)进行 JPEG 编解码。
源码位于 src/third/libjpeg/。
引擎包装层位于 src/image/jpeg.h / src/image/jpeg.cpp(shine::image::jpeg 类,实现 IImageLoader 接口)。
模块配置:Module/image/jpeg.json(静态库)。
优先使用 TurboJPEG API(
turbojpeg.h,tj3*函数族),而非底层jpeglib.h。
TurboJPEG API 更简洁、线程安全、并自动处理内存管理。
#include "turbojpeg.h" // TurboJPEG API(推荐)
#include "jpeglib.h" // 底层 IJG API(高级用途)
#include "jerror.h" // 错误常量
// 压缩
tjhandle compressor = tj3Init(TJINIT_COMPRESS);
// 解压
tjhandle decompressor = tj3Init(TJINIT_DECOMPRESS);
// 无损变换
tjhandle transformer = tj3Init(TJINIT_TRANSFORM);
使用完毕后 必须 调用
tj3Destroy(handle)释放资源。
tj3Set(handle, TJPARAM_QUALITY, 85); // 压缩质量 1-100
tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_422); // 色度子采样
tj3Set(handle, TJPARAM_FASTDCT, 1); // 使用快速 DCT
tj3Set(handle, TJPARAM_PROGRESSIVE, 1); // 渐进式 JPEG
int width = tj3Get(handle, TJPARAM_JPEGWIDTH); // 解压后读取
int height = tj3Get(handle, TJPARAM_JPEGHEIGHT);
int subsamp = tj3Get(handle, TJPARAM_SUBSAMP);
int precision = tj3Get(handle, TJPARAM_PRECISION);
| 枚举 | 通道数 | 说明 |
|---|---|---|
TJPF_RGB | 3 | R,G,B |
TJPF_BGR | 3 | B,G,R |
TJPF_RGBX | 4 | R,G,B,X(X 忽略) |
TJPF_RGBA | 4 | R,G,B,A(解压时 A=255) |
TJPF_BGRA | 4 | B,G,R,A |
TJPF_GRAY | 1 | 灰度 |
TJPF_CMYK | 4 | CMYK |
用
tjPixelSize[pixelFormat]获取每像素样本数。
| 枚举 | MCU 块大小 | 说明 |
|---|---|---|
TJSAMP_444 | 8×8 | 无子采样(最高质量) |
TJSAMP_422 | 16×8 | 水平 2:1 |
TJSAMP_420 | 16×16 | 水平+垂直 2:1(最常用) |
TJSAMP_GRAY | 8×8 | 灰度 |
TJSAMP_440 | 8×16 | 垂直 2:1 |
TJSAMP_411 | 32×8 | 水平 4:1 |
tjhandle handle = tj3Init(TJINIT_COMPRESS);
tj3Set(handle, TJPARAM_QUALITY, 90);
tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_420);
unsigned char* jpegBuf = nullptr; // TJ 自动分配
size_t jpegSize = 0;
int result = tj3Compress8(
handle,
rgbBuffer, // const unsigned char* 源像素数据
width, // 图像宽度
0, // pitch(0 = width * pixelSize)
height, // 图像高度
TJPF_RGB, // 源像素格式
&jpegBuf, // 输出 JPEG 缓冲区
&jpegSize // 输出 JPEG 大小
);
if (result == -1) {
// 错误处理
const char* err = tj3GetErrorStr(handle);
int errCode = tj3GetErrorCode(handle); // TJERR_WARNING 或 TJERR_FATAL
}
// 使用 jpegBuf/jpegSize...
tj3Free(jpegBuf); // 释放 TJ 分配的缓冲区
tj3Destroy(handle); // 释放实例
// 计算最大缓冲区大小
size_t maxSize = tj3JPEGBufSize(width, height, TJSAMP_420);
unsigned char* jpegBuf = (unsigned char*)tj3Alloc(maxSize);
size_t jpegSize = maxSize;
tj3Set(handle, TJPARAM_NOREALLOC, 1); // 禁止重分配
tj3Compress8(handle, src, w, 0, h, TJPF_RGB, &jpegBuf, &jpegSize);
// jpegSize 现在包含实际 JPEG 大小
tj3Free(jpegBuf);
tjhandle handle = tj3Init(TJINIT_DECOMPRESS);
// 第一步:读取 JPEG 头
int result = tj3DecompressHeader(handle, jpegBuf, jpegSize);
if (result == -1) { /* 错误处理 */ }
int width = tj3Get(handle, TJPARAM_JPEGWIDTH);
int height = tj3Get(handle, TJPARAM_JPEGHEIGHT);
int subsamp = tj3Get(handle, TJPARAM_SUBSAMP);
// 第二步:分配目标缓冲区
int pixelFormat = TJPF_RGBA;
size_t bufSize = width * height * tjPixelSize[pixelFormat];
auto dstBuf = std::make_unique<unsigned char[]>(bufSize);
// 第三步:解压
result = tj3Decompress8(
handle,
jpegBuf, // JPEG 数据
jpegSize, // JPEG 大小
dstBuf.get(), // 目标缓冲区
0, // pitch(0 = width * pixelSize)
pixelFormat // 目标像素格式
);
if (result == -1) {
const char* err = tj3GetErrorStr(handle);
}
tj3Destroy(handle);
int numFactors;
tjscalingfactor* factors = tj3GetScalingFactors(&numFactors);
// 选择 1/2 缩放
tjscalingfactor half = {1, 2};
tj3SetScalingFactor(handle, half);
// 计算缩放后的尺寸
int scaledW = TJSCALED(width, half);
int scaledH = TJSCALED(height, half);
// 分配缩放后大小的缓冲区并解压
tj3DecompressHeader(handle, jpegBuf, jpegSize);
tjregion crop = {0, 0, 256, 256}; // x 必须是 MCU 块宽度的倍数
tj3SetCroppingRegion(handle, crop);
// 解压只输出裁剪区域
tj3Decompress8(handle, jpegBuf, jpegSize, dstBuf, 0, TJPF_RGB);
tjhandle handle = tj3Init(TJINIT_TRANSFORM);
tjtransform xform = {};
xform.op = TJXOP_ROT90; // 旋转 90°
xform.options = TJXOPT_TRIM | TJXOPT_CROP; // 裁切不完整的 MCU 块
unsigned char* dstBuf = nullptr;
size_t dstSize = 0;
tj3Transform(handle, jpegBuf, jpegSize, 1, &dstBuf, &dstSize, &xform);
tj3Free(dstBuf);
tj3Destroy(handle);
变换操作:TJXOP_NONE, TJXOP_HFLIP, TJXOP_VFLIP, TJXOP_TRANSPOSE, TJXOP_TRANSVERSE, TJXOP_ROT90, TJXOP_ROT180, TJXOP_ROT270
// RGB → YUV
tjhandle handle = tj3Init(TJINIT_COMPRESS);
tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_420);
size_t yuvSize = tj3YUVBufSize(width, 1, height, TJSAMP_420);
auto yuvBuf = std::make_unique<unsigned char[]>(yuvSize);
tj3EncodeYUV8(handle, rgbBuf, width, 0, height, TJPF_RGB, yuvBuf.get(), 1);
// YUV → RGB
tjhandle dec = tj3Init(TJINIT_DECOMPRESS);
tj3Set(dec, TJPARAM_SUBSAMP, TJSAMP_420);
tj3DecodeYUV8(dec, yuvBuf.get(), 1, rgbOut, width, 0, height, TJPF_RGB);
// 严格模式:警告也停止
tj3Set(handle, TJPARAM_STOPONWARNING, 1);
// 检查错误
if (tj3Compress8(...) == -1) {
int code = tj3GetErrorCode(handle);
const char* msg = tj3GetErrorStr(handle);
if (code == TJERR_FATAL) {
// 不可恢复
} else {
// TJERR_WARNING: 输出可能损坏但部分可用
}
}
// 限制最大像素数,防止内存耗尽攻击
tj3Set(handle, TJPARAM_MAXPIXELS, 100 * 1024 * 1024); // 1亿像素
// 限制渐进式 JPEG 扫描次数
tj3Set(handle, TJPARAM_SCANLIMIT, 500);
// 限制中间缓冲区内存
tj3Set(handle, TJPARAM_MAXMEMORY, 512); // 512 MB
引擎的 shine::image::jpeg 类(在 src/image/jpeg.h)是一个手写的 JPEG 解码器,实现了 IImageLoader 接口:
class jpeg : public loader::IImageLoader {
bool loadFromFile(const char* filePath) override;
bool loadFromMemory(const void* data, size_t size) override;
void unload() override;
std::expected<void, std::string> decode() override; // → RGBA
std::expected<std::vector<uint8_t>, std::string> decodeRGB() override;
uint32_t getWidth() const noexcept override;
uint32_t getHeight() const noexcept override;
const std::vector<uint8_t>& getImageData() const noexcept override;
};
使用方式: