ESP-IDFを使ってみる

JPEG Decorder


ESP-IDFにはUndocumentな機能が沢山有ります。
ESP-IDFのコンポーネントには、こちらのTiny JPEG Decompressorが含まれています。
正確にはESP32用のコンポーネントには含まれていますが、ESP32-S2用のコンポーネントには含まれていません。
espressifに問合せしたら、ESP32-S2のROMが小さいから含めることが出来ないとの回答でした。
公開されているライブラリの最新版は2019年3月版ですが、ESP-IDFに付属するライブラリは2012年版で少し古いです。 

ようやくこのライブラリの使い方が分かったので紹介します。
使用するAPIは以下の2つです。
JRESULT jd_prepare (
  JDEC* jdec,            /* Pointer to blank decompression object */
  uint16_t(*infunc)(JDEC*,uint8_t*,uint16_t), /* Pointer to input function */
  void* work,            /* Pointer to work area */
  uint16_t sz_work,      /* Size of the work area */
  void* device           /* Device identifier for the session */
);

JRESULT jd_decomp (
  JDEC* jdec,             /* Pointer to valid decompressor object */
  uint16_t(*outfunc)(JDEC*,void*,JRECT*), /* Pointer to output function */
  uint8_t scale           /* Scaling factor */
);


ESP-IDFには、こ ちらのdecode_image.cで、このライブラリを使っています。
このサンプルはSPI Masterドライバーのサンプルで、WROVER-KITボード上の320x240 LCDに、
いくつかの単純なグラフィックを表示するサンプルですが、こんなところにTiny JPEG Decompressorが使われているとは...

サンプルコードでは以下の様になっています。
    //Prepare and decode the jpeg.
    r = jd_prepare(&decoder, infunc, work, WORKSZ, (void *)&jd);
    if (r != JDR_OK) {
        ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", r);
        ret = ESP_ERR_NOT_SUPPORTED;
        goto err;
    }
    r = jd_decomp(&decoder, outfunc, 0);
    if (r != JDR_OK && r != JDR_FMT1) {
        ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", r);
        ret = ESP_ERR_NOT_SUPPORTED;
        goto err;
    }


jd_prepare()の最初の引数はJDEC型の変数で、こ こで定義されています。
jd_prepare()の2番目の引数は変換前のJPEGイメージを読み込むためのCallBack関数へのポインターです。
jd_prepare()の3番目の引数はJPEG Decorderが内部で使用するワーク領域で事前にアロケートしておく必要が有ります。
jd_prepare()の4番目の引数はこのワーク領域のサイズです。JPEGファイルのサイズとは関係ありません。
jd_prepare()の5番目の引数はアプリが使用するための変数領域で自由に使うことができます。
サンプルでは以下の構造体のポインターを渡しています。
構造体のメンバーは好きに決めることができます。
//Data that is passed from the decoder function to the infunc/outfunc functions.
typedef struct {
    const unsigned char *inData; //Pointer to jpeg data
    uint16_t inPos;              //Current position in jpeg data
    uint16_t **outData;          //Array of IMAGE_H pointers to arrays of IMAGE_W 16-bit pixel values
    int outW;                    //Width of the resulting file
    int outH;                    //Height of the resulting file
} JpegDev;

jd_decomp()の最初の引数はJDEC型の変数です。
jd_decomp()の2番目の引数は変換後のイメージデータが渡ってくるCallBack関数へのポインターです。
このCallBackの中で変換後のJPEGイメージを処理します。
jd_decomp()の3番目の引数はスケーリングで、0から3の値を指定することができます。
0以外の値を指定した時は、1/2 1/4 1/8の倍率でイメージの縮小を行います。



jd_prepare()を実行すると以下のCallBackが必要に応じて呼ばれます。
最初の引数にJDEC型の変数が渡ってきて、deviceのメンバーにアプリが使用するための変数領域が格納されています。
bufにlen分の変換前JPEGデータを設定して戻ります。
なぜか、時々bufのアドレスがNULLになるときが有りますが、NULLの時は変換前データの読み込みをスキップします。
この例では埋め込まれたJPEGデータからデータを取り出していますが、JPEGファイルを変換する場合、
ここでファイルから読んでbufに格納することになります。
//Input function for jpeg decoder. Just returns bytes from the inData field of the JpegDev structure.
static uint16_t infunc(JDEC *decoder, uint8_t *buf, uint16_t len)
{
    //Read bytes from input file
    JpegDev *jd = (JpegDev *)decoder->device;
    if (buf != NULL) {
        memcpy(buf, jd->inData + jd->inPos, len);
    }
    jd->inPos += len;
    return len;
}

jd_decomp()を実行すると、変換を開始します。
JPEGのDecordは少しずつ行われ、変換が終わるごとに以下のCallBackが呼ばれます。
bitmapに変換後のイメージデータが、rectは変換された場所の情報(矩形領域)がこ の形式で渡ってきます。
以下の例ではin[0]がRED、in[1]がGreen、in[2]がBlueのデータで、それをRGB565の形式に変換して、
さらに上位の8ビットと下位の8ビットをSWAPしてから、outDataに格納しています。
//Output function. Re-encodes the RGB888 data from the decoder as big-endian RGB565 and
//stores it in the outData array of the JpegDev structure.
static uint16_t outfunc(JDEC *decoder, void *bitmap, JRECT *rect)
{
    JpegDev *jd = (JpegDev *)decoder->device;
    uint8_t *in = (uint8_t *)bitmap;
    for (int y = rect->top; y <= rect->bottom; y++) {
        for (int x = rect->left; x <= rect->right; x++) {
            //We need to convert the 3 bytes in `in` to a rgb565 value.
            uint16_t v = 0;
            v |= ((in[0] >> 3) << 11);
            v |= ((in[1] >> 2) << 5);
            v |= ((in[2] >> 3) << 0);
            //The LCD wants the 16-bit value in big-endian, so swap bytes
            v = (v >> 8) | (v << 8);
            jd->outData[y][x] = v;
            in += 3;
        }
    }
    return 1;
}

このコードを利用してSPI TFTへJPEGイメージを表示するサンプルをこちらで公開してい ます。




ESP-IDF Ver5で JPEG decode libraryは IDF Component Registry から提供されるようになり、
esp32s2やesp32c2では、これらを利用できるようになりました。
以下のコードでROM Component を利用するか、IDF Component Registryを利用するかを切り替えることができます。
#include "esp_rom_caps.h"

#if CONFIG_JD_USE_ROM
/* When supported in ROM, use ROM functions */
#if defined(ESP_ROM_HAS_JPEG_DECODE)
#include "rom/tjpgd.h"
#else
#error Using JPEG decoder from ROM is not supported for selected target. Please select external code in menuconfig.
#endif

/* use EXTRA functions */
#else
#include "tjpgd.h"
#endif

ただし、IDF Component Registryを利用できるのは、ESP-IDF Ver5からで、Ver4では利用きません。
まとめるとこの様になります。
特にesp32s2の扱いが厄介で、Ver4/Ver5のどちらでも動くようにするのは、かなり面倒です。

esp32 esp32s2 esp32s3 esp32h2 esp32c2 esp32c3 esp32c6
ESP-IDF Ver4.4 ROMライブラリ 利用できない ROMライブラリ 未サポート 未サポート ROMライブラリ 未サポート
ESP-IDF Ver5.0 ROMライブラリ 外部ライブラリ ROMライブラリ ROMライブラリ 外部ライブラリ ROMライブラリ ROMライブラリ

CONFIG_JD_USE_ROM=n にすることで、Ver5では常に外部ライブラリを使うようになりますが、esp32では外部ライブラリは正しく動きません。

続く...