feat(libjpeg_turbo): add JPEG image EXIF data parsing (#5263)

Co-authored-by: liujp <liujp@xiaomi.com>
This commit is contained in:
Lemon
2024-03-10 14:54:20 +08:00
committed by GitHub
parent 803ec3e059
commit 30793d712f
7 changed files with 199 additions and 23 deletions
+168 -21
View File
@@ -12,6 +12,7 @@
#include "lv_libjpeg_turbo.h"
#include <stdio.h>
#include <jpeglib.h>
#include <jpegint.h>
#include <setjmp.h>
/*********************
@@ -36,15 +37,27 @@ static lv_result_t decoder_info(lv_image_decoder_t * decoder, const void * src,
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static lv_draw_buf_t * decode_jpeg_file(const char * filename);
static bool get_jpeg_size(const char * filename, uint32_t * width, uint32_t * height);
static uint8_t * read_file(const char * filename, uint32_t * size);
static bool get_jpeg_head_info(const char * filename, uint32_t * width, uint32_t * height, uint32_t * orientation);
static bool get_jpeg_size(uint8_t * data, uint32_t data_size, uint32_t * width, uint32_t * height);
static bool get_jpeg_direction(uint8_t * data, uint32_t data_size, uint32_t * orientation);
static void rotate_buffer(lv_draw_buf_t * decoded, uint8_t * buffer, uint32_t line_index, uint32_t angle);
static void error_exit(j_common_ptr cinfo);
/**********************
* STATIC VARIABLES
**********************/
const int JPEG_EXIF = 0x45786966; /* Exif data structure tag */
const int JPEG_BIG_ENDIAN_TAG = 0x4d4d;
const int JPEG_LITTLE_ENDIAN_TAG = 0x4949;
/**********************
* MACROS
**********************/
#define TRANS_32_VALUE(big_endian, data) big_endian ? \
((*(data) << 24) | (*((data) + 1) << 16) | (*((data) + 2) << 8) | *((data) + 3)) : \
(*(data) | (*((data) + 1) << 8) | (*((data) + 2) << 16) | (*((data) + 3) << 24))
#define TRANS_16_VALUE(big_endian, data) big_endian ? \
((*(data) << 8) | *((data) + 1)) : (*(data) | (*((data) + 1) << 8))
/**********************
* GLOBAL FUNCTIONS
@@ -121,15 +134,16 @@ static lv_result_t decoder_info(lv_image_decoder_t * decoder, const void * src,
uint32_t width;
uint32_t height;
uint32_t orientation = 0;
if(!get_jpeg_size(fn, &width, &height)) {
if(!get_jpeg_head_info(fn, &width, &height, &orientation)) {
return LV_RESULT_INVALID;
}
/*Save the data in the header*/
header->cf = LV_COLOR_FORMAT_RGB888;
header->w = width;
header->h = height;
header->w = (orientation % 180) ? height : width;
header->h = (orientation % 180) ? width : height;
return LV_RESULT_OK;
}
@@ -193,7 +207,7 @@ static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t *
lv_cache_release(dsc->cache, dsc->cache_entry, NULL);
}
static uint8_t * alloc_file(const char * filename, uint32_t * size)
static uint8_t * read_file(const char * filename, uint32_t * size)
{
uint8_t * data = NULL;
lv_fs_file_t f;
@@ -262,7 +276,9 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
/* More stuff */
JSAMPARRAY buffer; /* Output row buffer */
int row_stride; /* physical row width in output buffer */
uint32_t image_angle = 0; /* image rotate angle */
lv_draw_buf_t * decoded = NULL;
@@ -273,7 +289,7 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
*/
uint32_t data_size;
uint8_t * data = alloc_file(filename, &data_size);
uint8_t * data = read_file(filename, &data_size);
if(data == NULL) {
LV_LOG_WARN("can't load file %s", filename);
return NULL;
@@ -300,6 +316,12 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
lv_free(data);
return NULL;
}
/* Get rotate angle from Exif data */
if(!get_jpeg_direction(data, data_size, &image_angle)) {
LV_LOG_WARN("read jpeg orientation failed.");
}
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress(&cinfo);
@@ -344,12 +366,11 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
/* Make a one-row-high sample array that will go away when done with image */
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
decoded = lv_draw_buf_create(cinfo.output_width, cinfo.output_height, LV_COLOR_FORMAT_RGB888, LV_STRIDE_AUTO);
uint32_t buf_width = (image_angle % 180) ? cinfo.output_height : cinfo.output_width;
uint32_t buf_height = (image_angle % 180) ? cinfo.output_width : cinfo.output_height;
decoded = lv_draw_buf_create(buf_width, buf_height, LV_COLOR_FORMAT_RGB888, LV_STRIDE_AUTO);
if(decoded != NULL) {
uint8_t * cur_pos = decoded->data;
size_t stride = cinfo.output_width * JPEG_PIXEL_SIZE;
uint32_t line_index = 0;
/* while (scan lines remain to be read) */
/* jpeg_read_scanlines(...); */
@@ -364,8 +385,9 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
jpeg_read_scanlines(&cinfo, buffer, 1);
/* Assume put_scanline_someplace wants a pointer and sample count. */
lv_memcpy(cur_pos, buffer[0], stride);
cur_pos += decoded->header.stride;
rotate_buffer(decoded, buffer[0], line_index, image_angle);
line_index++;
}
}
@@ -397,25 +419,39 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
return decoded;
}
static bool get_jpeg_size(const char * filename, uint32_t * width, uint32_t * height)
static bool get_jpeg_head_info(const char * filename, uint32_t * width, uint32_t * height, uint32_t * orientation)
{
struct jpeg_decompress_struct cinfo;
error_mgr_t jerr;
uint8_t * data = NULL;
uint32_t data_size;
data = alloc_file(filename, &data_size);
data = read_file(filename, &data_size);
if(data == NULL) {
return false;
}
if(!get_jpeg_size(data, data_size, width, height)) {
LV_LOG_WARN("read jpeg size failed.");
}
if(!get_jpeg_direction(data, data_size, orientation)) {
LV_LOG_WARN("read jpeg orientation failed.");
}
lv_free(data);
return JPEG_HEADER_OK;
}
static bool get_jpeg_size(uint8_t * data, uint32_t data_size, uint32_t * width, uint32_t * height)
{
struct jpeg_decompress_struct cinfo;
error_mgr_t jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = error_exit;
if(setjmp(jerr.jb)) {
LV_LOG_WARN("read jpeg head failed");
jpeg_destroy_decompress(&cinfo);
lv_free(data);
return false;
}
@@ -435,9 +471,120 @@ static bool get_jpeg_size(const char * filename, uint32_t * width, uint32_t * he
jpeg_destroy_decompress(&cinfo);
lv_free(data);
return JPEG_HEADER_OK;
}
return (ret == JPEG_HEADER_OK);
static bool get_jpeg_direction(uint8_t * data, uint32_t data_size, uint32_t * orientation)
{
struct jpeg_decompress_struct cinfo;
error_mgr_t jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = error_exit;
if(setjmp(jerr.jb)) {
LV_LOG_WARN("read jpeg orientation failed");
jpeg_destroy_decompress(&cinfo);
return false;
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, data, data_size);
jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
cinfo.marker->read_markers(&cinfo);
jpeg_saved_marker_ptr marker = cinfo.marker_list;
while(marker != NULL) {
if(marker->marker == JPEG_APP0 + 1) {
JOCTET FAR * app1_data = marker->data;
if(TRANS_32_VALUE(true, app1_data) == JPEG_EXIF) {
uint16_t endian_tag = TRANS_16_VALUE(true, app1_data + 4 + 2);
if(!(endian_tag == JPEG_LITTLE_ENDIAN_TAG || endian_tag == JPEG_BIG_ENDIAN_TAG)) {
jpeg_destroy_decompress(&cinfo);
return false;
}
bool is_big_endian = endian_tag == JPEG_BIG_ENDIAN_TAG;
/* first ifd offset addr : 4bytes(Exif) + 2bytes(0x00) + 2bytes(align) + 2bytes(tag mark) */
unsigned int offset = TRANS_32_VALUE(is_big_endian, app1_data + 8 + 2);
/* ifd base : 4bytes(Exif) + 2bytes(0x00) */
unsigned char * ifd = 0;
do {
/* ifd start: 4bytes(Exif) + 2bytes(0x00) + offset value(2bytes(align) + 2bytes(tag mark) + 4bytes(offset size)) */
unsigned int entry_offset = 4 + 2 + offset + 2;
if(entry_offset >= marker->data_length) {
jpeg_destroy_decompress(&cinfo);
return false;
}
ifd = app1_data + entry_offset;
unsigned short num_entries = TRANS_16_VALUE(is_big_endian, ifd - 2);
if(entry_offset + num_entries * 12 >= marker->data_length) {
jpeg_destroy_decompress(&cinfo);
return false;
}
for(int i = 0; i < num_entries; i++) {
unsigned short tag = TRANS_16_VALUE(is_big_endian, ifd);
if(tag == 0x0112) {
/* ifd entry: 12bytes = 2bytes(tag number) + 2bytes(kind of data) + 4bytes(number of components) + 4bytes(data)
* orientation kind(0x03) of data is unsigned short */
int dirc = TRANS_16_VALUE(is_big_endian, ifd + 2 + 2 + 4);
switch(dirc) {
case 1:
*orientation = 0;
break;
case 3:
*orientation = 180;
break;
case 6:
*orientation = 90;
break;
case 8:
*orientation = 270;
break;
default:
*orientation = 0;
}
}
ifd += 12;
}
offset = TRANS_32_VALUE(is_big_endian, ifd);
} while(offset != 0);
}
break;
}
marker = marker->next;
}
jpeg_destroy_decompress(&cinfo);
return JPEG_HEADER_OK;
}
static void rotate_buffer(lv_draw_buf_t * decoded, uint8_t * buffer, uint32_t line_index, uint32_t angle)
{
if(angle == 90) {
for(uint32_t x = 0; x < decoded->header.h; x++) {
uint32_t dst_index = x * decoded->header.stride + (decoded->header.w - line_index - 1) * JPEG_PIXEL_SIZE;
lv_memcpy(decoded->data + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
}
else if(angle == 180) {
for(uint32_t x = 0; x < decoded->header.w; x++) {
uint32_t dst_index = (decoded->header.h - line_index - 1) * decoded->header.stride + x * JPEG_PIXEL_SIZE;
lv_memcpy(decoded->data + dst_index, buffer + (decoded->header.w - x - 1) * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
}
else if(angle == 270) {
for(uint32_t x = 0; x < decoded->header.h; x++) {
uint32_t dst_index = (decoded->header.h - x - 1) * decoded->header.stride + line_index * JPEG_PIXEL_SIZE;
lv_memcpy(decoded->data + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
}
else {
lv_memcpy(decoded->data + line_index * decoded->header.stride, buffer, decoded->header.stride);
}
}
static void error_exit(j_common_ptr cinfo)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+31 -2
View File
@@ -19,10 +19,39 @@ static void create_images(void)
lv_obj_clean(lv_screen_active());
lv_obj_t * img;
lv_obj_t * label;
img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo.jpg");
lv_obj_center(img);
lv_obj_align(img, LV_ALIGN_CENTER, -150, -150);
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "jpeg with exif orientation 0");
lv_obj_align(label, LV_ALIGN_CENTER, -150, -100);
img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo_with_exif_orientation_180.jpg");
lv_obj_align(img, LV_ALIGN_CENTER, 150, -150);
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "jpeg with exif orientation 180");
lv_obj_align(label, LV_ALIGN_CENTER, 150, -100);
img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo_with_exif_orientation_90.jpg");
lv_obj_align(img, LV_ALIGN_CENTER, -150, 40);
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "jpeg with exif orientation 90");
lv_obj_align(label, LV_ALIGN_CENTER, -150, 150);
img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo_with_exif_orientation_270.jpg");
lv_obj_align(img, LV_ALIGN_CENTER, 150, 40);
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "jpeg with exif orientation 270");
lv_obj_align(label, LV_ALIGN_CENTER, 150, 150);
}
void test_jpg_2(void)
@@ -44,7 +73,7 @@ void test_jpg_2(void)
TEST_ASSERT_EQUAL_SCREENSHOT("libs/jpg_2.png");
TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 32);
TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 64);
/* Re-add tjpgd decoder */
lv_tjpgd_init();