2015年10月25日

ESP8266でGIF画像をデコードする

esp826611.jpg

JPEG画像に続いて、今度はGIF画像のデコードを試してみました。

GIF画像の圧縮・展開は、結構メモリが必要になります。
具体的には、最低でも25KB以上は必要です。

まず、GIFは最大256色を使いますが、色番号に対応する色はRGB各8ビットで表します。
従って256×3=768バイトを、カラーテーブルで使用します。

また、GIF画像は辞書を使う圧縮方式を使っています。
この辞書のエントリ数は最大で4096となっています。
1つの辞書エントリはコード3つ(そのエントリ自身と、次のエントリと前のエントリ)分のスペースを必要とします。
コードは最大12ビットですので、コード1つに2バイトを割り当てるとすると、辞書のためのメモリは約24KBほど必要になります。

なお、今回は使っていませんが、辞書を使わずに毎回復号コードを手続き的に求める方法を考案されている方もおられます。

ユニシス特許に抵触しないGIF画像のデコード

この方法を実装したLZ-Rというデコーダも上記ホームページで公開されています。


ESP8266はRAMを96KB積んでおり、Arduino環境で動かす場合は、そのうち80KB程度をユーザープログラムで使えますので、上記の辞書用メモリは何とか確保できそうです。

マイコンで使えそうなデコーダを、いろいろ既存のライブラリから探してみたのですが、意外と難航しました。
まずライブラリ自体が結構大きく多機能なものが多く、最低限の機能だけを実装したものがなかなか見当たりません。
また、デコードの方法も、展開後の画像全体を保持するバッファを渡すような形態が一般的です。
QVGA画像程度でも、全体を保持しようとすると320x240x3=230KBほどのRAMが必要となりますので、ESP8266ではこの方法はちょっと使えません。

ということで相当探し回った結果、Particleというマイコンボードのフォーラムで、使えそうな小さなデコーダを見つけました。
(ちなみに以前はParticleではなくSparkという名前でしたが、改名したようです。)

Parsing images from SD - General - Particle (formerly Spark)

このコードを元に、少々修正してESP8266で動くようにしました。
デコーダは別ファイルに分けており、メインのスケッチは以下のようになります。


#include <arduino.h>
#include <SPI.h>
#include <FS.h>
#include "gif.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ILI9341.h> // Hardware-specific library

#define TFT_CS     15
#define TFT_RST    5
#define TFT_DC     4

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

GIF gifdecoder;  


void setup() {
 File f;

 pinMode(TFT_RST, OUTPUT);
 digitalWrite(TFT_RST, LOW);
 digitalWrite(TFT_RST, HIGH);

 Serial.begin(115200);
 Serial.println("");

 tft.begin();
 // read diagnostics (optional but can help debug problems)
 uint8_t x = tft.readcommand8(ILI9341_RDMODE);
 Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDMADCTL);
 Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDPIXFMT);
 Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDIMGFMT);
 Serial.print("Image Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDSELFDIAG);
 Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

 tft.fillScreen(ILI9341_BLACK);

 SPIFFS.begin();
 f = SPIFFS.open("/test.gif", "r");
 if (f) {
   Serial.println("File Open OK");
   gifDraw(f);
 }
}

void gifDraw(File f) {
 GIF_IMAGE g;
 
 gifdecoder.init_decoder(f, &g);
 Serial.print("Image Width: ");
 Serial.println(gifdecoder.imgWidth);
 Serial.print("Image Height: ");
 Serial.println(gifdecoder.imgHeight);

 tft.setAddrWindow(0,0,g.imageDesc.IMAGE_WIDTH-1, g.imageDesc.IMAGE_HEIGHT-1);
 gifdecoder.drawGIFImage(drawpixel);
}

void drawpixel(unsigned char r, unsigned char g, unsigned char b)
{
 tft.pushColor(tft.color565(r, g, b));
}


void loop() {
 // put your main code here, to run repeatedly:

}


SPIFFS上で、"/test.gif"というファイルをデコードしてILI9340ベースのTFT LCDに表示します。

デコーダに、ピクセル描画関数へのポインタを渡す構成にしていますので、デコーダ自体は表示デバイスには依存しません。(ファイルシステムのAPIには依存しています。)

QVGAサイズの画像を表示させたときの速度はこんな感じです。


ちなみにアニメーションGIFには対応していません。
アニメーションGIFについては、小さなアニメGIFをマイコンとマトリクスLEDで表示する「Aurora」というプロジェクトがありますので、そのデコーダが参考にできるかもしれません。

pixelmatix/aurora

Overview | SmartMatrix Animated GIF Player | Adafruit Learning System

ここで使われているのはTeensyというボードで、CPUはARMですがRAMは64KBと、ESP8266よりも若干貧弱なスペックです。

今回使ったデコーダライブラリ(修正後)も、ちょっと長いですがそのまま掲載しておきます。

gif.h:
#ifndef __GIF_H__
#define __GIF_H__

typedef void(*gif_draw_pixel_t)(unsigned char r, unsigned char g, unsigned char b);

typedef struct{
unsigned short SCREEN_WIDTH;
unsigned short SCREEN_HEIGHT;
unsigned char BYTE_5;
unsigned char BACKGROUND;
unsigned char ASPECT_RATIO;
} SCREEN_DESC;

typedef struct{
unsigned short LEFT;
unsigned short UP;
unsigned short IMAGE_WIDTH;
unsigned short IMAGE_HEIGHT;
unsigned char BYTE_9;
} IMAGE_DESC;

typedef struct{
unsigned char R;
unsigned char G;
unsigned char B;
} COLOR;

typedef struct{
SCREEN_DESC screenDesc;
IMAGE_DESC imageDesc;
} GIF_IMAGE;

typedef struct{
unsigned short code;
unsigned short prevCode;
unsigned short nextCode;
} DICTIONARY_ENTRY;

class GIF {
private:
unsigned char BUFFER[16];
unsigned short PrimaryCodeSize, PrimaryDictSize;
unsigned short currentCodeSize, currentDictSize;

unsigned short code, oldcode = 0;
unsigned short code1, code2;
unsigned short code_CLEAR, code_END;

COLOR g_GlobalColorTable[256];

#define MAX_DICT_SIZE 4098
DICTIONARY_ENTRY Dictionary[MAX_DICT_SIZE];
unsigned char bitsRemaining;
unsigned short getNextCode(void);
int checkSignature(char*);
File g_fileObject;
gif_draw_pixel_t gif_draw_pixel;

void read_file(File, unsigned char *, int);
unsigned char read_code;

unsigned char byte5;
unsigned char byte9;
unsigned char currentDataSectionLength;

public:
int init_decoder(File, GIF_IMAGE *);
int drawGIFImage(gif_draw_pixel_t);
unsigned char counter;
void GIFDrawPixel(unsigned char);
unsigned short imgWidth, imgHeight;
};

#endif


gif.cpp:

#include <FS.h>
#include "gif.h"

int GIF::init_decoder(File fileObject, GIF_IMAGE * gifImage) {
unsigned int i;
unsigned char bpp;
unsigned short minLZWCodeLength;
unsigned int GCT_size;

g_fileObject = fileObject;

read_file(g_fileObject, BUFFER, 6);
if (checkSignature((char*)BUFFER) != 1) return -1;
 
read_file(g_fileObject, BUFFER, 7);

(*gifImage).screenDesc.SCREEN_WIDTH = (BUFFER[1] << 8) + BUFFER[0];
(*gifImage).screenDesc.SCREEN_HEIGHT = (BUFFER[3] << 8) + BUFFER[2];
(*gifImage).screenDesc.BYTE_5 = BUFFER[4];
(*gifImage).screenDesc.BACKGROUND = BUFFER[5];
(*gifImage).screenDesc.ASPECT_RATIO = BUFFER[6];

byte5 = (*gifImage).screenDesc.BYTE_5;

bpp = (byte5 & 0x07);

GCT_size = 2 << bpp;

if (byte5 & 0x80){

for (i = 0; i < GCT_size; i++){
read_file(g_fileObject, BUFFER, 3);
g_GlobalColorTable[i].R = BUFFER[0];
g_GlobalColorTable[i].G = BUFFER[1];
g_GlobalColorTable[i].B = BUFFER[2];
}
}
else return -1;

do {
read_file(g_fileObject, BUFFER, 1);
} while (BUFFER[0] != 0x2C);

read_file(g_fileObject, BUFFER, 11);

(*gifImage).imageDesc.LEFT = (BUFFER[1] << 8) + BUFFER[0];
(*gifImage).imageDesc.UP = (BUFFER[3] << 8) + BUFFER[2];
(*gifImage).imageDesc.IMAGE_WIDTH = (BUFFER[5] << 8) + BUFFER[4];
(*gifImage).imageDesc.IMAGE_HEIGHT = (BUFFER[7] << 8) + BUFFER[6];
(*gifImage).imageDesc.BYTE_9 = BUFFER[8];

imgWidth = (*gifImage).imageDesc.IMAGE_WIDTH;
imgHeight = (*gifImage).imageDesc.IMAGE_HEIGHT;
byte9 = (*gifImage).imageDesc.BYTE_9;

minLZWCodeLength = BUFFER[9] + 1;
currentDataSectionLength = BUFFER[10];

code_CLEAR = GCT_size;
code_END = GCT_size + 1;
PrimaryDictSize = GCT_size + 2;
PrimaryCodeSize = minLZWCodeLength;
currentCodeSize = minLZWCodeLength;

return 0;
}

void GIF::read_file(File fileObject, unsigned char *buf, int count){
 fileObject.read(buf,count);
}

int GIF::drawGIFImage(gif_draw_pixel_t draw_pixel_func){
unsigned int i;

gif_draw_pixel = draw_pixel_func;
currentDictSize = PrimaryDictSize;

counter = 0;

for (i = 0; i < MAX_DICT_SIZE; i++)
Dictionary[i].prevCode = Dictionary[i].nextCode = 0;

bitsRemaining = 0;

while ((code = getNextCode()) != code_END){


if (code == code_CLEAR){
currentCodeSize = PrimaryCodeSize;
currentDictSize = PrimaryDictSize;
oldcode = getNextCode();

if (oldcode > currentDictSize){
return -3;
}
GIFDrawPixel(oldcode);
continue;
}


if (code < currentDictSize){
code1 = code;
code2 = 0;

while (code1 >= PrimaryDictSize){
Dictionary[code1 - PrimaryDictSize].nextCode = code2;
code2 = code1;
code1 = Dictionary[code1 - PrimaryDictSize].prevCode;
if (code1 >= code2)
return -3;
}

GIFDrawPixel(code1);
while (code2 != 0){
GIFDrawPixel(Dictionary[code2 - PrimaryDictSize].code);
code2 = Dictionary[code2 - PrimaryDictSize].nextCode;
}
Dictionary[currentDictSize - PrimaryDictSize].code = code1;
Dictionary[currentDictSize - PrimaryDictSize].prevCode = oldcode;
++currentDictSize;

if (currentDictSize == MAX_DICT_SIZE) return -2;

if ((currentDictSize) == (0x0001 << currentCodeSize))
++currentCodeSize;
if (currentCodeSize > 12)
currentCodeSize = 12;

oldcode = code;
}
else {
code1 = oldcode;
code2 = 0;

while (code1 >= PrimaryDictSize){
Dictionary[code1 - PrimaryDictSize].nextCode = code2;
code2 = code1;
code1 = Dictionary[code1 - PrimaryDictSize].prevCode;
if (code1 >= code2)
return -3;
}

GIFDrawPixel(code1);
while (code2 != 0){
GIFDrawPixel(Dictionary[code2 - PrimaryDictSize].code);
code2 = Dictionary[code2 - PrimaryDictSize].nextCode;
}
GIFDrawPixel(code1);

Dictionary[currentDictSize - PrimaryDictSize].code = code1;
Dictionary[currentDictSize - PrimaryDictSize].prevCode = oldcode;
++currentDictSize;
//std::cout << "dictionary size: " << std::dec << currentDictSize << std::endl;

if (currentDictSize == MAX_DICT_SIZE) return -2;


if ((currentDictSize) == (0x0001 << currentCodeSize))
++currentCodeSize;

if (currentCodeSize > 12)
currentCodeSize = 12;

oldcode = code;
}
}
return 0;
}


int GIF::checkSignature(char * IN){
char signature[7];
int i;

for (i = 0; i <= 6; i++)
signature[i] = IN[i];

if ((strcmp(signature, "GIF87a") == 0) || (strcmp(signature, "GIF89a") == 0))
return 1;
else
return 0;
}

unsigned short GIF::getNextCode(void){
unsigned int retval = 0, temp;

if (bitsRemaining >= currentCodeSize){
retval = (read_code & ((0x01 << currentCodeSize) - 1));
read_code >>= currentCodeSize;
bitsRemaining -= currentCodeSize;
}
else {
retval = (read_code & ((0x01 << bitsRemaining) - 1));
read_file(g_fileObject, &read_code, 1);
++counter;
if (counter == currentDataSectionLength){
counter = 0;
read_file(g_fileObject, &currentDataSectionLength, 1);
}

if ((currentCodeSize - bitsRemaining) <= 8){
temp = (read_code & ((0x01 << (currentCodeSize - bitsRemaining)) - 1));
retval += (temp << bitsRemaining);
read_code >>= (currentCodeSize - bitsRemaining);
bitsRemaining = 8 - (currentCodeSize - bitsRemaining);
}
else {
retval += (read_code << bitsRemaining);
read_file(g_fileObject, &read_code, 1);
++counter;
if (counter == currentDataSectionLength){
counter = 0;
read_file(g_fileObject, &currentDataSectionLength, 1);
}
retval += ((read_code & ((0x01 << (currentCodeSize - bitsRemaining - 8)) - 1)) << (bitsRemaining + 8));
read_code >>= (currentCodeSize - bitsRemaining - 8);
bitsRemaining = 8 - (currentCodeSize - bitsRemaining - 8);
}
}
return retval;
}

void GIF::GIFDrawPixel(unsigned char code){
COLOR rgbColor;

rgbColor = g_GlobalColorTable[code];
(*gif_draw_pixel)(rgbColor.R, rgbColor.G, rgbColor.B);
}

posted by boochow at 23:53| Comment(2) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年10月17日

Visual Studioを触って気分転換

vs2013.png

このところArduino IDEばかり使ってきたので、ちょっと気分転換にMicrosoftのVisual Studioを使ってみました。

といっても、こちらの連載記事を一通りやってみたというだけです。

Insider.NET > 連載:簡単! Visual Studio 2013入門 - @IT

私のWindows上での開発経験は乏しく、5年以上前にTurbo Delphiをしばらく使っていたのみです。
しかし、Visual Studioは感覚的にはTurbo Delphiと大きくは違わず、意外とすんなり入れました。

やっぱりデバッガでステップ実行や変数のインスペクタが使えるのは良いですね。
ArduinoはIDEといっても実質的にはデバッガ機能は皆無ですので、デバッグではストレスが溜まります。
posted by boochow at 20:40| Comment(0) | Windows | このブログの読者になる | 更新情報をチェックする

2015年10月13日

ILI9340液晶の表示の高速化

前回の続きです。

前回の最後のほうで
現在は1ピクセルずつdrawPixelで書いている部分をsetAddrWindowとpushColorで書き直せば、もう少し高速化できると思います。
と書きましたが、大した手間でもないのでやってみました。

描画のループ部分で使う描画命令をdrawPixelからpushColorへ変更した結果、2倍ほど高速になりました。
あまり綺麗ではありませんが、動画を載せておきます。


修正したスケッチは以下に貼っておきます。

#include <arduino.h>
#include <SPI.h>
#include <FS.h>
#include <JPEGDecoder.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ILI9341.h> // Hardware-specific library

#define TFT_CS     15
#define TFT_RST    5
#define TFT_DC     4

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

void setup() {
 pinMode(TFT_RST, OUTPUT);
 digitalWrite(TFT_RST, LOW);
 digitalWrite(TFT_RST, HIGH);

 Serial.begin(115200);
 Serial.println("");
 delay(10);

 tft.begin();
 // read diagnostics (optional but can help debug problems)
 uint8_t x = tft.readcommand8(ILI9341_RDMODE);
 Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDMADCTL);
 Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDPIXFMT);
 Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDIMGFMT);
 Serial.print("Image Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDSELFDIAG);
 Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

 tft.fillScreen(ILI9341_BLACK);

 SPIFFS.begin();
 jpegDraw("/test1.jpg");
}

void jpegDraw(char* filename) {
 char str[100];
 uint8 *pImg;
 int x,y,bx,by;
 
 // Decoding start
 JpegDec.decode(filename,0);

 // Image Information
 Serial.print("Width     :");
 Serial.println(JpegDec.width);
 Serial.print("Height    :");
 Serial.println(JpegDec.height);
 Serial.print("Components:");
 Serial.println(JpegDec.comps);
 Serial.print("MCU / row :");
 Serial.println(JpegDec.MCUSPerRow);
 Serial.print("MCU / col :");
 Serial.println(JpegDec.MCUSPerCol);
 Serial.print("Scan type :");
 Serial.println(JpegDec.scanType);
 Serial.print("MCU width :");
 Serial.println(JpegDec.MCUWidth);
 Serial.print("MCU height:");
 Serial.println(JpegDec.MCUHeight);
 Serial.println("");
 
 sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
 Serial.println(str);

 // Raw Image Data
 while(JpegDec.read()){
   pImg = JpegDec.pImage ;
   x = JpegDec.MCUx * JpegDec.MCUWidth;
   y = JpegDec.MCUy * JpegDec.MCUHeight;
   tft.setAddrWindow(x, y, x+JpegDec.MCUWidth-1, y+JpegDec.MCUHeight-1);
   for(by=0; by<JpegDec.MCUHeight; by++){
     for(bx=0; bx<JpegDec.MCUWidth; bx++){
       if(JpegDec.comps == 1){ // Grayscale          
         tft.pushColor(tft.color565(pImg[0], pImg[0], pImg[0]));
       }else{ // RGB
         tft.pushColor(tft.color565(pImg[0], pImg[1], pImg[2]));
       }        
       pImg += JpegDec.comps ;
     }
   }
 }
}
   
void loop() {
}

posted by boochow at 01:01| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする

2015年10月12日

激安QVGA液晶をESP8266で使う

esp826610.jpg


中国の通販サイトaliexpressで、QVGA(320x240)の2.2インチカラーLCDが送料込で5ドル少々だったので購入してみました。
インタフェースはSPIで、コントローラはILI9340Cということで、たぶん以下の記事で使われているのと同じものだと思います。

Cheap TFT 2.2 inch Display on Arduino (ILI9340C or ILI9341) - All

ちなみに購入したお店はこちらです。

New 2.2 inch 2.2 240x320 SPI TFT LCD Display Module ILI9341 PCB 5V/3.3V 51/AVR/STM32/ARM/PIC Free Shipping-in LCD Modules from Electronic Components & Supplies on Aliexpress.com | Alibaba Group

ピン配列は以下のようになっています。

1 : VCC
2 : GND
3 : CS
4 : RESET
5 : DC/RS
6 : SDI/MOSI
7 : SCK
8 : LED
9 : SDO/MISO

esp826610-01.jpg


SPIですので、基本的に接続方法はこれまで使っていたST7735のLCDと同じです(ピンの呼称が若干違いますが)。
ESP8266とは、以下のように接続しました。

LCD / ESP8266
CS / IO15
RST / IO5
DC / IO4
SDI / IO13
SCK / IO14
SDO / IO12

SDOはST7735では使っていませんでしたが、ILI9340ではLCDから情報を受け取るために使います。
他に、VCCとLEDを電源(3.3V)、GNDを電源のGNDに接続します。
LEDは抵抗が入っているかどうか怪しいので、5Vには直接接続しないほうが良いかもしれません。


ドライバはArduino IDE for ESP8266に最初から含まれています。
ただしILI9340ではなくILI9341用のものですが、問題なく動作しました。

Arduino/libraries/Adafruit_ILI9341 at esp8266 ・ esp8266/Arduino

また、スケッチ例として「graphicstest_esp8266」が含まれています。
ESP8266用のスケッチですので、結線情報など若干変更すれば動作しました。
ちなみに変更部分は以下の箇所です。

#include <Arduino.h>
#include <SPI.h>  //この行を追加
#include <Adafruit_GFX.h>  //この行を追加
#include <Adafruit_ILI9341.h>

// For the Adafruit shield, these are the default.
//結線を以下の通り変更
#define TFT_CS     15
#define TFT_RST    5
#define TFT_DC     4



私の環境では、上記の変更だけでは動作したりしなかったりという現象があったのですが、この記事のコメント欄を見ると、リセットはHighにしないと動作しない場合があるようです。

そこで、setupルーチンの冒頭に以下のコードも追加しています。

void setup() {
 pinMode(TFT_RST, OUTPUT);
 digitalWrite(TFT_RST, LOW);
 digitalWrite(TFT_RST, HIGH);



これでスケッチ例が動作するようになったので、前回のJPEG表示プログラムもILI9340用に書き換えて、320x240ピクセルの画像を表示させてみました。

動作結果がページ先頭の写真です。
表示させている画像は画像符号化の世界ではおなじみのレナさんです。

書き換えのポイントは以下の通りです。

・includeするファイルの変更
#include <Adafruit_ILI9341.h>

・TFT液晶のオブジェクト
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

・液晶のリセットコード追加(前述)
・TFT初期化
  tft.begin();

・色変換の関数名
            tft.drawPixel(x, y, tft.color565(

・LCDが動作しているかどうかの診断(スケッチ例graphicstest_sep8266からコピー)

表示に1秒以上かかりますが、現在は1ピクセルずつdrawPixelで書いている部分をsetAddrWindowとpushColorで書き直せば、もう少し高速化できると思います。

最後にこのスケッチの全体を掲載しておきます。
JPEGファイルは、ファイル名test1.jpgで、あらかじめSPIFFSへ格納しておいてください。


#include <arduino.h>
#include <SPI.h>
#include <FS.h>
#include <JPEGDecoder.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ILI9341.h> // Hardware-specific library

#define TFT_CS     15
#define TFT_RST    5
#define TFT_DC     4

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

void setup() {
 pinMode(TFT_RST, OUTPUT);
 digitalWrite(TFT_RST, LOW);
 digitalWrite(TFT_RST, HIGH);

 Serial.begin(115200);
 Serial.println("");
 delay(10);

 tft.begin();
 // read diagnostics (optional but can help debug problems)
 uint8_t x = tft.readcommand8(ILI9341_RDMODE);
 Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDMADCTL);
 Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDPIXFMT);
 Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDIMGFMT);
 Serial.print("Image Format: 0x"); Serial.println(x, HEX);
 x = tft.readcommand8(ILI9341_RDSELFDIAG);
 Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

 tft.fillScreen(ILI9341_BLACK);

 SPIFFS.begin();
 jpegDraw("/test1.jpg");
}

void jpegDraw(char* filename) {
 char str[100];
 uint8 *pImg;
 int x,y,bx,by;
 
 // Decoding start
 JpegDec.decode(filename,0);

 // Image Information
 Serial.print("Width     :");
 Serial.println(JpegDec.width);
 Serial.print("Height    :");
 Serial.println(JpegDec.height);
 Serial.print("Components:");
 Serial.println(JpegDec.comps);
 Serial.print("MCU / row :");
 Serial.println(JpegDec.MCUSPerRow);
 Serial.print("MCU / col :");
 Serial.println(JpegDec.MCUSPerCol);
 Serial.print("Scan type :");
 Serial.println(JpegDec.scanType);
 Serial.print("MCU width :");
 Serial.println(JpegDec.MCUWidth);
 Serial.print("MCU height:");
 Serial.println(JpegDec.MCUHeight);
 Serial.println("");
 
 sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
 Serial.println(str);

 // Raw Image Data
 while(JpegDec.read()){
   pImg = JpegDec.pImage ;

   for(by=0; by<JpegDec.MCUHeight; by++){
     for(bx=0; bx<JpegDec.MCUWidth; bx++){
       
       x = JpegDec.MCUx * JpegDec.MCUWidth + bx;
       y = JpegDec.MCUy * JpegDec.MCUHeight + by;
       
       if(x<JpegDec.width && y<JpegDec.height){
         if(JpegDec.comps == 1){ // Grayscale          
           tft.drawPixel(x, y, tft.color565(pImg[0], pImg[0], pImg[0]));
         }else{ // RGB
           tft.drawPixel(x, y, tft.color565(pImg[0], pImg[1], pImg[2]));
         }
       }
       
       pImg += JpegDec.comps ;
     }
   }
 }
}
   
void loop() {
}

posted by boochow at 17:39| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする

ESP8266でJPEG画像をTFT LCDに表示する

esp826609.jpg

前回はSPIFFSの中に格納したBMPファイルを表示させましたが、JPEG、GIF、PNGなどのファイルも表示させたくなりますね。
調べてみたところ、これらの画像形式の中でESP8266で比較的扱いやすいのはJPEGのようです。

JPEG、GIF、PNGはいずれも圧縮アルゴリズムを使っていますが、GIFとPNGは辞書形式の圧縮符号化方式(GIFはLZW、PNGはZlib)を使っています。
このタイプの圧縮アルゴリズムは、辞書をRAM上に展開すると、比較的大きなワーキングメモリが必要になります。
一方、JPEGは計算量はGIFやPNGよりも大きい代わりに、ワーキングメモリはそれほど大きくないようです。
picojpegというメモリ消費の少ないデコーダ実装があり、これをArduino上で利用した例も見つかりました。

Arduino用JPEGデコーダ | 遊舎工房

今回は、上記のデコーダを使って前回同様にSPIFFS上に置いたJPEGファイルをLCDに表示させてみました。


まず、デコーダライブラリを下記からダウンロードしてArduino IDEに追加(スケッチ→Include Library→Add .ZIP Library...)します。

MakotoKurauchi/JPEGDecoder

このライブラリはSDカードでの利用が想定されていますので、SPIFFSで使うために、以下の部分をちょこっと変更します。
変更するファイルは「マイドキュメント\Arduino\Libraries\JPEGDecoder-master\JPEGDecoder.cpp」です。

修正前: #include <SD.h>
修正後: #include <FS.h>

修正前: g_pInFile = SD.open(pFilename, FILE_READ);
修正後: g_pInFile = SPIFFS.open(pFilename, "r");


スケッチ本体は以下のようになります。
もともとのライブラリに、デコード結果をシリアルポートに表示する分かりやすいサンプルがありましたので、シリアルポート出力の代わりにLCDに点を打つだけで動作させることができました。

SPIFFSの中に"test1.jpg"というファイルを入れておくと、それをST7735ベースのLCDに表示します。
ESP8266のSPIFFSへのファイル転送のやり方は以前の記事を参照してください。


#include <arduino.h>
#include <SPI.h>
#include <FS.h>
#include <JPEGDecoder.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library

#define TFT_CS     15
#define TFT_RST    5
#define TFT_DC     4

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);

void setup() {
 Serial.begin(115200);
 Serial.println("");
 delay(10);

 tft.initR(INITR_BLUETAB);
 tft.fillScreen(ST7735_BLACK);

 SPIFFS.begin();
 jpegDraw("/test1.jpg");
}

void jpegDraw(char* filename) {
 char str[100];
 uint8 *pImg;
 int x,y,bx,by;
 
 // Decoding start
 JpegDec.decode(filename,0);

 // Image Information
 Serial.print("Width     :");
 Serial.println(JpegDec.width);
 Serial.print("Height    :");
 Serial.println(JpegDec.height);
 Serial.print("Components:");
 Serial.println(JpegDec.comps);
 Serial.print("MCU / row :");
 Serial.println(JpegDec.MCUSPerRow);
 Serial.print("MCU / col :");
 Serial.println(JpegDec.MCUSPerCol);
 Serial.print("Scan type :");
 Serial.println(JpegDec.scanType);
 Serial.print("MCU width :");
 Serial.println(JpegDec.MCUWidth);
 Serial.print("MCU height:");
 Serial.println(JpegDec.MCUHeight);
 Serial.println("");
 
 sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
 Serial.println(str);

 // Raw Image Data
 while(JpegDec.read()){
   pImg = JpegDec.pImage ;

   for(by=0; by<JpegDec.MCUHeight; by++){
     for(bx=0; bx<JpegDec.MCUWidth; bx++){
       
       x = JpegDec.MCUx * JpegDec.MCUWidth + bx;
       y = JpegDec.MCUy * JpegDec.MCUHeight + by;
       
       if(x<JpegDec.width && y<JpegDec.height){
         if(JpegDec.comps == 1){ // Grayscale          
           tft.drawPixel(x, y, tft.Color565(pImg[0], pImg[0], pImg[0]));
         }else{ // RGB
           tft.drawPixel(x, y, tft.Color565(pImg[0], pImg[1], pImg[2]));
         }
       }
       
       pImg += JpegDec.comps ;
     }
   }
 }
}
   
void loop() {
}

posted by boochow at 11:32| Comment(0) | ESP8266 | このブログの読者になる | 更新情報をチェックする
人気記事