modules/py_image: Add support to control jpeg subsampling.

This commit is contained in:
Kwabena W. Agyeman 2024-03-17 15:42:48 -07:00
parent 31f58cc99a
commit b037b2208a
6 changed files with 51 additions and 27 deletions

View File

@ -166,7 +166,7 @@ void framebuffer_update_jpeg_buffer() {
.pixels = jpeg_framebuffer->pixels
};
// Note: lower quality saves USB bandwidth and results in a faster IDE FPS.
bool overflow = jpeg_compress(src, &dst, jpeg_framebuffer->quality, false);
bool overflow = jpeg_compress(src, &dst, jpeg_framebuffer->quality, false, JPEG_SUBSAMPLING_AUTO);
if (overflow) {
// JPEG buffer overflowed, reduce JPEG quality for the next frame

View File

@ -753,6 +753,13 @@ bool image_get_mask_pixel(image_t *ptr, int x, int y);
#define JPEG_422_YCBCR_MCU_SIZE ((JPEG_444_GS_MCU_SIZE) * 4)
#define JPEG_420_YCBCR_MCU_SIZE ((JPEG_444_GS_MCU_SIZE) * 6)
typedef enum jpeg_subsampling {
JPEG_SUBSAMPLING_AUTO = 0,
JPEG_SUBSAMPLING_444 = 0x11, // Chroma subsampling 4:4:4 (No subsampling)
JPEG_SUBSAMPLING_422 = 0x21, // Chroma subsampling 4:2:2
JPEG_SUBSAMPLING_420 = 0x22, // Chroma subsampling 4:2:0
} jpeg_subsampling_t;
// Old Image Macros - Will be refactor and removed. But, only after making sure through testing new macros work.
// Image kernels
@ -964,12 +971,6 @@ typedef enum template_match {
SEARCH_DS, // Diamond search
} template_match_t;
typedef enum jpeg_subsample {
JPEG_SUBSAMPLE_1x1 = 0x11, // 1x1 chroma subsampling (No subsampling)
JPEG_SUBSAMPLE_2x1 = 0x21, // 2x2 chroma subsampling
JPEG_SUBSAMPLE_2x2 = 0x22, // 2x2 chroma subsampling
} jpeg_subsample_t;
typedef enum corner_detector_type {
CORNER_FAST,
CORNER_AGAST
@ -1180,7 +1181,7 @@ void imlib_hardware_jpeg_deinit();
void jpeg_get_mcu(image_t *src, int x_offset, int y_offset, int dx, int dy,
int8_t *Y0, int8_t *CB, int8_t *CR);
void jpeg_decompress(image_t *dst, image_t *src);
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc);
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling);
bool jpeg_is_valid(image_t *img);
int jpeg_clean_trailing_bytes(int bpp, uint8_t *data);
void jpeg_read_geometry(FIL *fp, image_t *img, const char *path, jpg_read_settings_t *rs);

View File

@ -1509,7 +1509,7 @@ static void jpeg_init(int quality) {
}
}
static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg_subsample_t jpeg_subsample) {
static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg_subsampling_t subsampling) {
// Number of components (1 or 3)
uint8_t nr_comp = (bpp == 1)? 1 : 3;
@ -1574,7 +1574,7 @@ static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg
jpeg_put_bytes(jpeg_buf, m_sof0, sizeof(m_sof0));
for (int i = 0; i < nr_comp; i++) {
// Component ID, HV sampling, q table idx
jpeg_put_bytes(jpeg_buf, (uint8_t [3]) {i + 1, (i == 0 && bpp == 2)? jpeg_subsample:0x11, (i > 0)}, 3);
jpeg_put_bytes(jpeg_buf, (uint8_t [3]) {i + 1, (i == 0 && bpp == 2)? subsampling:0x11, (i > 0)}, 3);
}
@ -1613,7 +1613,7 @@ static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg
jpeg_put_bytes(jpeg_buf, (uint8_t [3]) {0x00, 0x3F, 0x0}, 3);
}
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling) {
#if (TIME_JPEG == 1)
mp_uint_t start = mp_hal_ticks_ms();
#endif
@ -1642,22 +1642,30 @@ bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
// Initialize quantization tables
jpeg_init(quality);
jpeg_subsample_t jpeg_subsample = JPEG_SUBSAMPLE_1x1;
if (src->is_color) {
if (subsampling == JPEG_SUBSAMPLING_AUTO) {
if (quality <= 35) {
jpeg_subsample = JPEG_SUBSAMPLE_2x2;
subsampling = JPEG_SUBSAMPLING_420;
} else if (quality < 60) {
jpeg_subsample = JPEG_SUBSAMPLE_2x1;
subsampling = JPEG_SUBSAMPLING_422;
} else {
subsampling = JPEG_SUBSAMPLING_444;
}
}
} else {
subsampling = JPEG_SUBSAMPLING_444;
}
jpeg_write_headers(&jpeg_buf, src->w, src->h, src->is_color ? 2 : 1, jpeg_subsample);
jpeg_write_headers(&jpeg_buf, src->w, src->h, src->is_color ? 2 : 1, subsampling);
int DCY = 0, DCU = 0, DCV = 0;
switch (jpeg_subsample) {
case JPEG_SUBSAMPLE_1x1: {
switch (subsampling) {
// Quiet GCC compiler warning (this is never reached)
case JPEG_SUBSAMPLING_AUTO: {
break;
}
case JPEG_SUBSAMPLING_444: {
int8_t YDU[JPEG_444_GS_MCU_SIZE];
int8_t UDU[JPEG_444_GS_MCU_SIZE];
int8_t VDU[JPEG_444_GS_MCU_SIZE];
@ -1683,7 +1691,7 @@ bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
}
break;
}
case JPEG_SUBSAMPLE_2x1: {
case JPEG_SUBSAMPLING_422: {
// color only
int8_t YDU[JPEG_444_GS_MCU_SIZE * 2];
int8_t UDU[JPEG_444_GS_MCU_SIZE * 2];
@ -1738,7 +1746,7 @@ bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
}
break;
}
case JPEG_SUBSAMPLE_2x2: {
case JPEG_SUBSAMPLING_420: {
// color only
int8_t YDU[JPEG_444_GS_MCU_SIZE * 4];
int8_t UDU[JPEG_444_GS_MCU_SIZE * 4];
@ -1972,7 +1980,7 @@ void jpeg_write(image_t *img, const char *path, int quality) {
// When jpeg_compress needs more memory than in currently allocated it
// will try to realloc. MP will detect that the pointer is outside of
// the heap and return NULL which will cause an out of memory error.
jpeg_compress(img, &out, quality, false);
jpeg_compress(img, &out, quality, false, JPEG_SUBSAMPLING_AUTO);
file_write(&fp, out.pixels, out.size);
fb_free(); // frees alloc in jpeg_compress()
}

View File

@ -181,7 +181,7 @@ void mjpeg_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *byt
// When jpeg_compress needs more memory than in currently allocated it
// will try to realloc. MP will detect that the pointer is outside of
// the heap and return NULL which will cause an out of memory error.
jpeg_compress(&temp, &dst_img, quality, true);
jpeg_compress(&temp, &dst_img, quality, true, JPEG_SUBSAMPLING_AUTO);
} else {
dst_img.size = img->size;
dst_img.data = img->data;

View File

@ -1025,6 +1025,9 @@ static mp_obj_t py_image_to(pixformat_t pixfmt, const uint16_t *default_color_pa
bool arg_e = py_helper_keyword_int(n_args, args, 13, kw_args,
MP_OBJ_NEW_QSTR(MP_QSTR_encode_for_ide), encode_for_ide_default);
jpeg_subsampling_t subsampling =
py_helper_keyword_int(n_args, args, 14, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_subsampling), JPEG_SUBSAMPLING_AUTO);
if (copy_obj) {
if (mp_obj_is_integer(copy_obj)) {
copy = mp_obj_get_int(copy_obj);
@ -1111,7 +1114,7 @@ static mp_obj_t py_image_to(pixformat_t pixfmt, const uint16_t *default_color_pa
(hint & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL);
}
if (((dst_img.pixfmt == PIXFORMAT_JPEG) && jpeg_compress(&temp, &dst_img_tmp, arg_q, false))
if (((dst_img.pixfmt == PIXFORMAT_JPEG) && jpeg_compress(&temp, &dst_img_tmp, arg_q, false, subsampling))
|| ((dst_img.pixfmt == PIXFORMAT_PNG) && png_compress(&temp, &dst_img_tmp))) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Compression Failed!"));
}
@ -7246,6 +7249,10 @@ static const mp_rom_map_elem_t globals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR_ROTATE_90), MP_ROM_INT(IMAGE_HINT_VFLIP | IMAGE_HINT_TRANSPOSE)},
{MP_ROM_QSTR(MP_QSTR_ROTATE_180), MP_ROM_INT(IMAGE_HINT_HMIRROR | IMAGE_HINT_VFLIP)},
{MP_ROM_QSTR(MP_QSTR_ROTATE_270), MP_ROM_INT(IMAGE_HINT_HMIRROR | IMAGE_HINT_TRANSPOSE)},
{MP_ROM_QSTR(MP_QSTR_JPEG_SUBSAMPLING_AUTO), MP_ROM_INT(JPEG_SUBSAMPLING_AUTO)},
{MP_ROM_QSTR(MP_QSTR_JPEG_SUBSAMPLING_444), MP_ROM_INT(JPEG_SUBSAMPLING_444)},
{MP_ROM_QSTR(MP_QSTR_JPEG_SUBSAMPLING_422), MP_ROM_INT(JPEG_SUBSAMPLING_422)},
{MP_ROM_QSTR(MP_QSTR_JPEG_SUBSAMPLING_420), MP_ROM_INT(JPEG_SUBSAMPLING_420)},
#ifdef IMLIB_FIND_TEMPLATE
{MP_ROM_QSTR(MP_QSTR_SEARCH_EX), MP_ROM_INT(SEARCH_EX)},
{MP_ROM_QSTR(MP_QSTR_SEARCH_DS), MP_ROM_INT(SEARCH_DS)},

View File

@ -105,7 +105,7 @@ static void jpeg_compress_data_ready(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOu
}
}
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling) {
#if (TIME_JPEG == 1)
mp_uint_t start = mp_hal_ticks_ms();
#endif
@ -132,10 +132,18 @@ bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) {
mcu_size = JPEG_444_YCBCR_MCU_SIZE;
JPEG_Info.ColorSpace = JPEG_YCBCR_COLORSPACE;
JPEG_Info.ChromaSubsampling = JPEG_444_SUBSAMPLING;
if (subsampling == JPEG_SUBSAMPLING_AUTO) {
if (quality < 60) {
mcu_size = JPEG_422_YCBCR_MCU_SIZE;
JPEG_Info.ChromaSubsampling = JPEG_422_SUBSAMPLING;
}
} else if (subsampling == JPEG_SUBSAMPLING_422) {
mcu_size = JPEG_422_YCBCR_MCU_SIZE;
JPEG_Info.ChromaSubsampling = JPEG_422_SUBSAMPLING;
} else if (subsampling == JPEG_SUBSAMPLING_420) {
// not supported
return true;
}
break;
default:
break;