Skip to content

Commit acb5249

Browse files
Merge pull request #498 from espressif/feature/esp_jpeg_prepare
feat(esp_jpeg): Added option to get image size without decoding it
2 parents c37fc0a + 3f723c5 commit acb5249

File tree

5 files changed

+149
-10
lines changed

5 files changed

+149
-10
lines changed

esp_jpeg/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.3.0
2+
3+
- Added option to get image size without decoding it
4+
15
## 1.2.1
26

37
- Fixed decoding of non-conforming 0xFFFF marker

esp_jpeg/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "1.2.1"
1+
version: "1.3.0"
22
description: "JPEG Decoder: TJpgDec"
33
url: https://github.com/espressif/idf-extra-components/tree/master/esp_jpeg/
44
dependencies:

esp_jpeg/include/jpeg_decoder.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -62,20 +62,20 @@ typedef struct esp_jpeg_image_cfg_s {
6262

6363
/**
6464
* @brief JPEG output info
65-
*
6665
*/
6766
typedef struct esp_jpeg_image_output_s {
6867
uint16_t width; /*!< Width of the output image */
6968
uint16_t height; /*!< Height of the output image */
69+
size_t output_len; /*!< Length of the output image in bytes */
7070
} esp_jpeg_image_output_t;
7171

7272
/**
7373
* @brief Decode JPEG image
7474
*
7575
* @note This function is blocking.
7676
*
77-
* @param cfg: Configuration structure
78-
* @param img: Output image info
77+
* @param[in] cfg: Configuration structure
78+
* @param[out] img: Output image info
7979
*
8080
* @return
8181
* - ESP_OK on success
@@ -84,6 +84,23 @@ typedef struct esp_jpeg_image_output_s {
8484
*/
8585
esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img);
8686

87+
/**
88+
* @brief Get information about the JPEG image
89+
*
90+
* Use this function to get the size of the JPEG image without decoding it.
91+
* Allocate a buffer of size img->output_len to store the decoded image.
92+
*
93+
* @note cfg->outbuf and cfg->outbuf_size are not used in this function.
94+
* @param[in] cfg: Configuration structure
95+
* @param[out] img: Output image info
96+
*
97+
* @return
98+
* - ESP_OK on success
99+
* - ESP_ERR_INVALID_ARG if cfg or img is NULL
100+
* - ESP_FAIL if there is an error in decoding JPEG
101+
*/
102+
esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img);
103+
87104
#ifdef __cplusplus
88105
}
89106
#endif

esp_jpeg/jpeg_decoder.c

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -65,6 +65,7 @@ static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format);
6565

6666
static unsigned int jpeg_decode_in_cb(JDEC *jd, uint8_t *buff, unsigned int nbyte);
6767
static jpeg_decode_out_t jpeg_decode_out_cb(JDEC *jd, void *bitmap, JRECT *rect);
68+
static inline uint16_t ldb_word(const void *ptr);
6869
/*******************************************************************************
6970
* Public API functions
7071
*******************************************************************************/
@@ -96,16 +97,17 @@ esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *im
9697
res = jd_prepare(&JDEC, jpeg_decode_in_cb, workbuf, workbuf_size, cfg);
9798
ESP_GOTO_ON_FALSE((res == JDR_OK), ESP_FAIL, err, TAG, "Error in preparing JPEG image! %d", res);
9899

99-
uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
100-
uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
100+
const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
101+
const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
101102

102103
/* Size of output image */
103-
uint32_t outsize = (JDEC.height / scale_div) * (JDEC.width / scale_div) * out_color_bytes;
104+
const uint32_t outsize = (JDEC.height / scale_div) * (JDEC.width / scale_div) * out_color_bytes;
104105
ESP_GOTO_ON_FALSE((outsize <= cfg->outbuf_size), ESP_ERR_NO_MEM, err, TAG, "Not enough size in output buffer!");
105106

106107
/* Size of output image */
107108
img->height = JDEC.height / scale_div;
108109
img->width = JDEC.width / scale_div;
110+
img->output_len = outsize;
109111

110112
/* Decode JPEG */
111113
res = jd_decomp(&JDEC, jpeg_decode_out_cb, cfg->out_scale);
@@ -119,6 +121,49 @@ esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *im
119121
return ret;
120122
}
121123

124+
esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img)
125+
{
126+
if (cfg == NULL || img == NULL) {
127+
return ESP_ERR_INVALID_ARG;
128+
} else if (cfg->indata == NULL || cfg->indata_size < 5) {
129+
return ESP_ERR_INVALID_ARG;
130+
}
131+
esp_err_t ret = ESP_FAIL;
132+
133+
if (ldb_word(cfg->indata) != 0xFFD8) {
134+
return ESP_FAIL; /* Err: SOI is not detected */
135+
}
136+
unsigned ofs = 2; // Start after SOI marker
137+
138+
while (true) {
139+
/* Get a JPEG marker */
140+
uint8_t *seg = cfg->indata + ofs; /* Segment pointer */
141+
unsigned short marker = ldb_word(seg); /* Marker */
142+
unsigned int len = ldb_word(seg + 2); /* Length field */
143+
if (len <= 2 || (marker >> 8) != 0xFF) {
144+
return ESP_FAIL;
145+
}
146+
ofs += 2 + len; /* Number of bytes loaded */
147+
if (ofs > cfg->indata_size) {
148+
return ESP_FAIL; // No more data
149+
}
150+
151+
if ((marker & 0xFF) == 0xC0) { /* SOF0 (baseline JPEG) */
152+
seg += 4; /* Skip marker and length field */
153+
154+
/* Size of output image */
155+
img->height = ldb_word(seg + 1);
156+
img->width = ldb_word(seg + 3);
157+
const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
158+
const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
159+
img->output_len = (img->height / scale_div) * (img->width / scale_div) * out_color_bytes;
160+
ret = ESP_OK;
161+
break;
162+
}
163+
}
164+
return ret;
165+
}
166+
122167
/*******************************************************************************
123168
* Private API functions
124169
*******************************************************************************/
@@ -234,3 +279,9 @@ static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format)
234279

235280
return 1;
236281
}
282+
283+
static inline uint16_t ldb_word(const void *ptr)
284+
{
285+
const uint8_t *p = (const uint8_t *)ptr;
286+
return ((uint16_t)p[0] << 8) | p[1];
287+
}

esp_jpeg/test_apps/main/tjpgd_test.c

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Unlicense OR CC0-1.0
55
*/
@@ -84,6 +84,64 @@ TEST_CASE("Test JPEG decompression library", "[esp_jpeg]")
8484
free(decoded);
8585
}
8686

87+
/**
88+
* @brief JPEG unknown size test
89+
*
90+
* This test case verifies the functionality of the JPEG decompression library
91+
* when decoding an image with unknown size. The image is decoded from a
92+
* JPEG file, and the output size is determined dynamically. The test checks
93+
* that the decoded image dimensions match the expected values and that the
94+
* pixel data is within an acceptable tolerance range.
95+
*/
96+
TEST_CASE("Test JPEG unknown size", "[esp_jpeg]")
97+
{
98+
unsigned char *decoded, *p;
99+
const unsigned char *o;
100+
101+
/* JPEG decode */
102+
esp_jpeg_image_cfg_t jpeg_cfg = {
103+
.indata = (uint8_t *)logo_jpg,
104+
.indata_size = logo_jpg_len,
105+
.out_format = JPEG_IMAGE_FORMAT_RGB888,
106+
};
107+
108+
// 1. Get required output size
109+
esp_jpeg_image_output_t outimg;
110+
esp_err_t err = esp_jpeg_get_image_info(&jpeg_cfg, &outimg);
111+
TEST_ASSERT_EQUAL(err, ESP_OK);
112+
TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len);
113+
TEST_ASSERT_EQUAL(outimg.width, TESTW);
114+
TEST_ASSERT_EQUAL(outimg.height, TESTH);
115+
116+
// 2. Allocate output buffer and assign it to the config
117+
decoded = malloc(outimg.output_len);
118+
TEST_ASSERT_NOT_NULL(decoded);
119+
jpeg_cfg.outbuf = decoded;
120+
jpeg_cfg.outbuf_size = outimg.output_len;
121+
122+
// 3. Decode the image
123+
err = esp_jpeg_decode(&jpeg_cfg, &outimg);
124+
TEST_ASSERT_EQUAL(err, ESP_OK);
125+
126+
/* Decoded image size */
127+
TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len);
128+
TEST_ASSERT_EQUAL(outimg.width, TESTW);
129+
TEST_ASSERT_EQUAL(outimg.height, TESTH);
130+
131+
p = decoded;
132+
o = logo_rgb888;
133+
for (int x = 0; x < outimg.width * outimg.height; x++) {
134+
/* The color can be +- 2 */
135+
TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]);
136+
TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]);
137+
TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]);
138+
139+
p += 3;
140+
o += 3;
141+
}
142+
free(decoded);
143+
}
144+
87145
#define WORKING_BUFFER_SIZE 4096
88146
TEST_CASE("Test JPEG decompression library: User defined working buffer", "[esp_jpeg]")
89147
{
@@ -224,6 +282,15 @@ TEST_CASE("Test JPEG decompression library: No Huffman tables", "[esp_jpeg]")
224282

225283
#endif
226284

285+
/**
286+
* @brief Invalid JPEG marker test
287+
*
288+
* This test case verifies the behavior of the JPEG decompression library
289+
* when encountering an invalid marker (0xFFFF) in the JPEG data stream.
290+
* The test uses a known JPEG image (camera_2_jpg) that contains this invalid
291+
* marker. The test checks whether the library can handle the invalid marker
292+
* gracefully and still decode the image correctly.
293+
*/
227294
TEST_CASE("Test JPEG invalid marker 0xFFFF", "[esp_jpeg]")
228295
{
229296
unsigned char *decoded;

0 commit comments

Comments
 (0)