mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-27 11:57:48 +08:00
feat(libjpeg_turbo): add JPEG image EXIF data parsing (#5263)
Co-authored-by: liujp <liujp@xiaomi.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
#include "lv_libjpeg_turbo.h"
|
#include "lv_libjpeg_turbo.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <jpeglib.h>
|
#include <jpeglib.h>
|
||||||
|
#include <jpegint.h>
|
||||||
#include <setjmp.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 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 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 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 void error_exit(j_common_ptr cinfo);
|
||||||
/**********************
|
/**********************
|
||||||
* STATIC VARIABLES
|
* 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
|
* 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
|
* 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 width;
|
||||||
uint32_t height;
|
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;
|
return LV_RESULT_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Save the data in the header*/
|
/*Save the data in the header*/
|
||||||
header->cf = LV_COLOR_FORMAT_RGB888;
|
header->cf = LV_COLOR_FORMAT_RGB888;
|
||||||
header->w = width;
|
header->w = (orientation % 180) ? height : width;
|
||||||
header->h = height;
|
header->h = (orientation % 180) ? width : height;
|
||||||
|
|
||||||
return LV_RESULT_OK;
|
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);
|
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;
|
uint8_t * data = NULL;
|
||||||
lv_fs_file_t f;
|
lv_fs_file_t f;
|
||||||
@@ -262,7 +276,9 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
|
|||||||
|
|
||||||
/* More stuff */
|
/* More stuff */
|
||||||
JSAMPARRAY buffer; /* Output row buffer */
|
JSAMPARRAY buffer; /* Output row buffer */
|
||||||
|
|
||||||
int row_stride; /* physical row width in output buffer */
|
int row_stride; /* physical row width in output buffer */
|
||||||
|
uint32_t image_angle = 0; /* image rotate angle */
|
||||||
|
|
||||||
lv_draw_buf_t * decoded = NULL;
|
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;
|
uint32_t data_size;
|
||||||
uint8_t * data = alloc_file(filename, &data_size);
|
uint8_t * data = read_file(filename, &data_size);
|
||||||
if(data == NULL) {
|
if(data == NULL) {
|
||||||
LV_LOG_WARN("can't load file %s", filename);
|
LV_LOG_WARN("can't load file %s", filename);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -300,6 +316,12 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
|
|||||||
lv_free(data);
|
lv_free(data);
|
||||||
return NULL;
|
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. */
|
/* Now we can initialize the JPEG decompression object. */
|
||||||
jpeg_create_decompress(&cinfo);
|
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 */
|
/* Make a one-row-high sample array that will go away when done with image */
|
||||||
buffer = (*cinfo.mem->alloc_sarray)
|
buffer = (*cinfo.mem->alloc_sarray)
|
||||||
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
|
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
|
||||||
|
uint32_t buf_width = (image_angle % 180) ? cinfo.output_height : cinfo.output_width;
|
||||||
decoded = lv_draw_buf_create(cinfo.output_width, cinfo.output_height, LV_COLOR_FORMAT_RGB888, LV_STRIDE_AUTO);
|
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) {
|
if(decoded != NULL) {
|
||||||
uint8_t * cur_pos = decoded->data;
|
uint32_t line_index = 0;
|
||||||
size_t stride = cinfo.output_width * JPEG_PIXEL_SIZE;
|
|
||||||
|
|
||||||
/* while (scan lines remain to be read) */
|
/* while (scan lines remain to be read) */
|
||||||
/* jpeg_read_scanlines(...); */
|
/* jpeg_read_scanlines(...); */
|
||||||
|
|
||||||
@@ -364,8 +385,9 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
|
|||||||
jpeg_read_scanlines(&cinfo, buffer, 1);
|
jpeg_read_scanlines(&cinfo, buffer, 1);
|
||||||
|
|
||||||
/* Assume put_scanline_someplace wants a pointer and sample count. */
|
/* Assume put_scanline_someplace wants a pointer and sample count. */
|
||||||
lv_memcpy(cur_pos, buffer[0], stride);
|
rotate_buffer(decoded, buffer[0], line_index, image_angle);
|
||||||
cur_pos += decoded->header.stride;
|
|
||||||
|
line_index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,25 +419,39 @@ static lv_draw_buf_t * decode_jpeg_file(const char * filename)
|
|||||||
return decoded;
|
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;
|
uint8_t * data = NULL;
|
||||||
uint32_t data_size;
|
uint32_t data_size;
|
||||||
data = alloc_file(filename, &data_size);
|
data = read_file(filename, &data_size);
|
||||||
if(data == NULL) {
|
if(data == NULL) {
|
||||||
return false;
|
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);
|
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||||
jerr.pub.error_exit = error_exit;
|
jerr.pub.error_exit = error_exit;
|
||||||
|
|
||||||
if(setjmp(jerr.jb)) {
|
if(setjmp(jerr.jb)) {
|
||||||
LV_LOG_WARN("read jpeg head failed");
|
LV_LOG_WARN("read jpeg head failed");
|
||||||
jpeg_destroy_decompress(&cinfo);
|
jpeg_destroy_decompress(&cinfo);
|
||||||
lv_free(data);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,9 +471,120 @@ static bool get_jpeg_size(const char * filename, uint32_t * width, uint32_t * he
|
|||||||
|
|
||||||
jpeg_destroy_decompress(&cinfo);
|
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)
|
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 |
@@ -19,10 +19,39 @@ static void create_images(void)
|
|||||||
lv_obj_clean(lv_screen_active());
|
lv_obj_clean(lv_screen_active());
|
||||||
|
|
||||||
lv_obj_t * img;
|
lv_obj_t * img;
|
||||||
|
lv_obj_t * label;
|
||||||
|
|
||||||
img = lv_image_create(lv_screen_active());
|
img = lv_image_create(lv_screen_active());
|
||||||
lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo.jpg");
|
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)
|
void test_jpg_2(void)
|
||||||
@@ -44,7 +73,7 @@ void test_jpg_2(void)
|
|||||||
|
|
||||||
TEST_ASSERT_EQUAL_SCREENSHOT("libs/jpg_2.png");
|
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 */
|
/* Re-add tjpgd decoder */
|
||||||
lv_tjpgd_init();
|
lv_tjpgd_init();
|
||||||
|
|||||||
Reference in New Issue
Block a user