From b037b2208ac5dad84a991236dbbf397a8898aaf9 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 17 Mar 2024 15:42:48 -0700 Subject: [PATCH] modules/py_image: Add support to control jpeg subsampling. --- src/omv/imlib/framebuffer.c | 2 +- src/omv/imlib/imlib.h | 15 ++++++++------- src/omv/imlib/jpege.c | 38 ++++++++++++++++++++++--------------- src/omv/imlib/mjpeg.c | 2 +- src/omv/modules/py_image.c | 9 ++++++++- src/omv/ports/stm32/jpeg.c | 12 ++++++++++-- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/omv/imlib/framebuffer.c b/src/omv/imlib/framebuffer.c index fcb59acf5..1f3e0fba3 100644 --- a/src/omv/imlib/framebuffer.c +++ b/src/omv/imlib/framebuffer.c @@ -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 diff --git a/src/omv/imlib/imlib.h b/src/omv/imlib/imlib.h index a30d91150..dde8f74d0 100644 --- a/src/omv/imlib/imlib.h +++ b/src/omv/imlib/imlib.h @@ -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); diff --git a/src/omv/imlib/jpege.c b/src/omv/imlib/jpege.c index 2a678a73e..bf9e4730c 100644 --- a/src/omv/imlib/jpege.c +++ b/src/omv/imlib/jpege.c @@ -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 (quality <= 35) { - jpeg_subsample = JPEG_SUBSAMPLE_2x2; - } else if (quality < 60) { - jpeg_subsample = JPEG_SUBSAMPLE_2x1; + if (subsampling == JPEG_SUBSAMPLING_AUTO) { + if (quality <= 35) { + subsampling = JPEG_SUBSAMPLING_420; + } else if (quality < 60) { + 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() } diff --git a/src/omv/imlib/mjpeg.c b/src/omv/imlib/mjpeg.c index 23cdcdcb3..7522ccac9 100644 --- a/src/omv/imlib/mjpeg.c +++ b/src/omv/imlib/mjpeg.c @@ -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; diff --git a/src/omv/modules/py_image.c b/src/omv/modules/py_image.c index 7aef7e85a..03e8d66bc 100644 --- a/src/omv/modules/py_image.c +++ b/src/omv/modules/py_image.c @@ -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)}, diff --git a/src/omv/ports/stm32/jpeg.c b/src/omv/ports/stm32/jpeg.c index 419e026b0..dca7dc868 100644 --- a/src/omv/ports/stm32/jpeg.c +++ b/src/omv/ports/stm32/jpeg.c @@ -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,9 +132,17 @@ 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 (quality < 60) { + 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: