# ESP_JPEG

ESP_JPEG is Espressif's lightweight JPEG encoder and decoder library. The memory and CPU loading are optimized to make better use of Espressif chips.

## Features

Encoder:  
    - Support variety of width and height to encoder  
    - Support RGB888 RGBA YCbYCr YCbY2YCrY2 GRAY raw data  
    - Support YUV444 YUV422 YUV420 subsampling  
    - Support quality(1-100)  
    - Support 0, 90 180 270 degree clockwise rotation, under the follow cases.  
     &emsp; 1. src_type = JPEG_PIXEL_FORMAT_YCbYCr, subsampling = JPEG_SUBSAMPLE_420 and width and height are multiply of 16.  
     &emsp; 2. src_type = JPEG_PIXEL_FORMAT_YCbYCr, subsampling = JPEG_SUBSAMPLE_GRAY and width and height are multiply of 8.  
    - Support mono-task and dual-task  
    - Support two mode encoder, respectively block encoder and one image encoder.  

Decoder:  
    - Support variety of width and height to decoder  
    - Support one and three channels decoder  
    - Support RGB888 RGB565(big endian) RGB565(little endian) CbYCrY raw data output  
    - Support 0, 90 180 270 degree clockwise rotation, under width and height are multiply of 8.  

## Usage

### JPEG Encoder

Example of JPEG encoder function call using `jpeg_enc_process` interface.  

```c
void esp_jpeg_encoder_one_picture(uint8_t quality)
{
    // configure encoder
    jpeg_enc_config_t jpeg_enc_cfg = DEFAULT_JPEG_ENC_CONFIG();
    uint8_t          *outbuf       = NULL;
    uint8_t          *inbuf        = NULL;
    int               image_size   = jpeg_enc_cfg.width * jpeg_enc_cfg.height * 2;
    int               out_size     = 100 * 1024;
    jpeg_enc_handle_t jpeg_enc     = NULL;
    jpeg_error_t      ret          = JPEG_ERR_OK;
    FILE             *in           = NULL;

    // allocate input buffer to fill original image stream
    if ((inbuf = (uint8_t *)jpeg_calloc_align(image_size, 16)) == NULL) {
        ESP_LOGE(TAG, "Allocate memory for inbuf fail. line %d", __LINE__);
        goto jpeg_example_exit;
    }
    in = fopen("in_file_name.yuv", "rb");
    if (in == NULL) {
        ESP_LOGE(TAG, "Open input file fail");
        goto jpeg_example_exit;
    }

    // allocate output buffer to fill encoded image stream
    outbuf = (uint8_t *)calloc(1, out_size);
    if (outbuf == NULL) {
        ESP_LOGE(TAG, "Allocate memory for outbuf fail. line %d", __LINE__);
        goto jpeg_example_exit;
    }

    // open
    ret = jpeg_enc_open(&jpeg_enc_cfg, &jpeg_enc);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG encoder open failed");
        goto jpeg_example_exit;
    }

    // process
    ret = jpeg_enc_process(jpeg_enc, inbuf, image_size, outbuf, out_size, &out_size);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG process error. ret %d", ret);
        goto jpeg_example_exit;
    }

jpeg_example_exit:
    jpeg_enc_close(jpeg_enc);
    jpeg_free_align(inbuf);
    if (in) {
        fclose(in);
    }
    if (outbuf) {
        free(outbuf);
    }
}
```

Example of JPEG encoder function call using `jpeg_enc_process_with_block` interface.  

```c
void esp_jpeg_encoder_one_picture_with_block(uint8_t quality)
{
    // configure encoder
    jpeg_enc_config_t jpeg_enc_cfg = DEFAULT_JPEG_ENC_CONFIG();
    uint8_t          *outbuf       = NULL;
    uint8_t          *inbuf        = NULL;
    int               block_size   = 0;
    int               outbuf_size  = 100 * 1024;
    int               out_size     = 0;
    jpeg_enc_handle_t jpeg_enc     = NULL;
    jpeg_error_t      ret          = JPEG_ERR_OK;
    int               in_offset    = 0;
    int               in_buf_size  = jpeg_enc_cfg.width * jpeg_enc_cfg.height * 2;
    int               num_times    = 0;
    FILE             *in           = NULL;
    FILE             *out          = NULL;

    // open
    ret = jpeg_enc_open(&jpeg_enc_cfg, &jpeg_enc);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG encoder open failed");
        goto jpeg_example_exit;
    }

    // outbuf
    if ((outbuf = (uint8_t *)calloc(1, outbuf_size)) == NULL) {
        ESP_LOGE(TAG, "Allocate memory for outbuf fail. line %d", __LINE__);
        goto jpeg_example_exit;
    }
    printf("output buffer: %p\n", outbuf);

    // inbuf
    block_size = jpeg_enc_get_block_size(jpeg_enc);
    if ((inbuf = (uint8_t *)jpeg_calloc_align(block_size, 16)) == NULL) {
        ESP_LOGE(TAG, "Allocate memory for inbuf fail. line %d", __LINE__);
        goto jpeg_example_exit;
    }

    num_times = in_buf_size / block_size;
    in = fopen("in_file_name.yuv", "rb");
    if (in == NULL) {
        ESP_LOGE(TAG, "Open input file fail");
        goto jpeg_example_exit;
    }
    out = fopen("out_file_name.yuv", "rb");
    if (out == NULL) {
        ESP_LOGE(TAG, "Open output file fail");
        goto jpeg_example_exit;
    }

    // process
    for (size_t j = 0; j < num_times; j++) {
        ret = fread(inbuf, 1, block_size, in);
        if (ret <= 0) {
            ret = fwrite(outbuf, 1, out_size, out);
            if (ret <= 0) {
                ESP_LOGE(TAG, "Write JPEG file fail");
            }
            goto jpeg_example_exit;
        }

        ret = jpeg_enc_process_with_block(jpeg_enc, inbuf, block_size, outbuf, outbuf_size, &out_size);
        if (ret < JPEG_ERR_OK) {
            ESP_LOGE(TAG, "JPEG process error. ret %d", ret);
            goto jpeg_example_exit;
        }
    }

jpeg_example_exit:
    jpeg_enc_close(jpeg_enc);
    if (in) {
        fclose(in);
    }
    if (out) {
        fclose(out);
    }
    if (inbuf) {
        jpeg_free_align(inbuf);
    }
    if (outbuf) {
        heap_caps_free(outbuf);
    }
}
```

### JPEG Decoder

Example of JPEG decoder function call.  

```c
// input_buf   input picture data
// len         input picture data length
// output_buf  allocated in `esp_jpeg_decoder_one_picture` but it won't to free. Please release this buffer after decoding is complete.
// out_len     output data length
jpeg_error_t esp_jpeg_decoder_one_picture(unsigned char *input_buf, int len, unsigned char **output_buf, int *out_len)
{
    unsigned char *out_buf = NULL;
    jpeg_error_t ret = JPEG_ERR_OK;
    jpeg_dec_io_t *jpeg_io = NULL;
    jpeg_dec_header_info_t *out_info = NULL;

    // Generate default configuration
    jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();

    // Empty handle to jpeg_decoder
    jpeg_dec_handle_t jpeg_dec = NULL;
    ret = jpeg_dec_open(&config, &jpeg_dec);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG open fail, ret = %d", ret);
        goto jpeg_dec_failed;
    }

    // Create io_callback handle
    jpeg_io = calloc(1, sizeof(jpeg_dec_io_t));
    if (jpeg_io == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }

    // Create out_info handle
    out_info = calloc(1, sizeof(jpeg_dec_header_info_t));
    if (out_info == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }

    // Set input buffer and buffer len to io_callback
    jpeg_io->inbuf = input_buf;
    jpeg_io->inbuf_len = len;

    // Parse jpeg picture header and get picture for user and decoder
    ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG parse header error, ret = %d", ret);
        goto jpeg_dec_failed;
    }

    // Calloc block output data buffer
    int output_len = 0;
    ret = jpeg_dec_get_outbuf_len(jpeg_dec, &output_len);
    if (ret != JPEG_ERR_OK || output_len == 0) {
        ESP_LOGE(TAG, "Get out buffer len fail");
        goto jpeg_dec_failed;
    }
    *out_len = output_len;

    out_buf = jpeg_calloc_align(*out_len, 16);
    if (out_buf == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }
    jpeg_io->outbuf = out_buf;
    *output_buf = out_buf;

    // Start decode jpeg raw data
    ret = jpeg_dec_process(jpeg_dec, jpeg_io);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG process error, ret = %d", ret);
        goto jpeg_dec_failed;
    }

    // Decoder deinitialize
jpeg_dec_failed:
    jpeg_dec_close(jpeg_dec);
    if (jpeg_io) {
        free(jpeg_io);
    }
    if (out_info) {
        free(out_info);
    }
    return ret;
}
```

Example of JPEG decoder function call when enable block mode.  

```c
// input_buf   input picture data
// len         input picture data length
jpeg_error_t esp_jpeg_decoder_one_picture_block(unsigned char *input_buf, int len)
{
    unsigned char *output_block = NULL;
    jpeg_error_t ret = JPEG_ERR_OK;
    jpeg_dec_io_t *jpeg_io = NULL;
    jpeg_dec_header_info_t *out_info = NULL;
    FILE *f_out = NULL;

    // Generate default configuration
    jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
    config.block_enable = true;

    // Empty handle to jpeg_decoder
    jpeg_dec_handle_t jpeg_dec = NULL;
    ret = jpeg_dec_open(&config, &jpeg_dec);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG open fail, ret = %d", ret);
        goto jpeg_dec_failed;
    }

    // Create io_callback handle
    jpeg_io = calloc(1, sizeof(jpeg_dec_io_t));
    if (jpeg_io == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }

    // Create out_info handle
    out_info = calloc(1, sizeof(jpeg_dec_header_info_t));
    if (out_info == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }

    // Set input buffer and buffer len to io_callback
    jpeg_io->inbuf = input_buf;
    jpeg_io->inbuf_len = len;

    // Parse jpeg picture header and get picture for user and decoder
    ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
    if (ret != JPEG_ERR_OK) {
        ESP_LOGE(TAG, "JPEG parse header error, ret = %d", ret);
        goto jpeg_dec_failed;
    }

    // Calloc block output data buffer
    int output_len = 0;
    ret = jpeg_dec_get_outbuf_len(jpeg_dec, &output_len);
    if (ret != JPEG_ERR_OK || output_len == 0) {
        ESP_LOGE(TAG, "Get out buffer len fail");
        goto jpeg_dec_failed;
    }

    output_block = jpeg_calloc_align(output_len, 16);
    if (output_block == NULL) {
        return ret;
    }
    jpeg_io->outbuf = output_block;

    // get process count
    int process_count = 0;
    ret = jpeg_dec_get_process_count(jpeg_dec, &process_count);
    if (ret != JPEG_ERR_OK || process_count == 0) {
        ESP_LOGE(TAG, "Get process count fail");
        goto jpeg_dec_failed;
    }

    // Open output file on SDCard, should init SDcard first
    f_out = fopen("/sdcard/dec_out.bin", "wb");
    if (f_out == NULL) {
        ret = JPEG_ERR_NO_MEM;
        goto jpeg_dec_failed;
    }

    // Decode jpeg data
    for (int block_cnt = 0; block_cnt < process_count; block_cnt++) {
        ret = jpeg_dec_process(jpeg_dec, jpeg_io);
        if (ret != JPEG_ERR_OK) {
            ESP_LOGE(TAG, "JPEG process error, ret = %d, line: %d", ret, __LINE__);
            goto jpeg_dec_failed;
        }

        // do something - to sdcard
        int written_data = fwrite(jpeg_io->outbuf, 1, jpeg_io->out_size, f_out);
        if (written_data != jpeg_io->out_size) {
            ESP_LOGE(TAG, "Write data to SDCard fail.");
            goto jpeg_dec_failed;
        }
    }

    // Decoder deinitialize
jpeg_dec_failed:
    jpeg_dec_close(jpeg_dec);
    if (jpeg_io) {
        free(jpeg_io);
    }
    if (out_info) {
        free(out_info);
    }
    jpeg_free_align(output_block);
    if (f_out) {
        fclose(f_out);
    }
    return ret;
}
```

