Files
JPEGDecoder/JPEGDecoder.cpp

465 lines
12 KiB
C++

/*
JPEGDecoder.cpp
JPEG Decoder for Arduino
https://github.com/MakotoKurauchi/JPEGDecoder
Public domain, Makoto Kurauchi <http://yushakobo.jp>
Latest version here:
https://github.com/Bodmer/JPEGDecoder
Bodmer (21/6/15): Adapted by Bodmer to display JPEGs on TFT (works with Mega and Due) but there
is a memory leak somewhere, crashes after decoding 1 file :-)
Bodmer (29/1/16): Now in a state with sufficient Mega and Due testing to release in the wild
Bodmer (various): Various updates and latent bugs fixed
Bodmer (14/1/17): Tried to merge ESP8266 and SPIFFS support from Frederic Plante's broken branch,
worked on ESP8266, but broke the array handling :-(
Bodmer (14/1/17): Scrapped all FP's updates, extended the built-in approach to using different
data sources (currently array, SD files and/or SPIFFS files)
Bodmer (14/1/17): Added ESP8266 support and SPIFFS as a source, added configuration option to
swap bytes to support fast image transfer to TFT using ESP8266 SPI writePattern().
Bodmer (15/1/17): Now supports ad hoc use of SPIFFS, SD and arrays without manual configuration.
Bodmer (19/1/17): Add support for filename being String type
Bodmer (20/1/17): Correct last mcu block corruption (thanks stevstrong for tracking that bug down!)
Bodmer (20/1/17): Prevent deleting the pImage pointer twice (causes an exception on ESP8266),
tidy up code.
*/
#include "JPEGDecoder.h"
#include "picojpeg.h"
JPEGDecoder JpegDec;
JPEGDecoder::JPEGDecoder(){
mcu_x = 0 ;
mcu_y = 0 ;
is_available = 0;
thisPtr = this;
}
JPEGDecoder::~JPEGDecoder(){
if (pImage) delete pImage;
pImage = NULL;
}
uint8_t JPEGDecoder::pjpeg_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) {
JPEGDecoder *thisPtr = JpegDec.thisPtr ;
thisPtr->pjpeg_need_bytes_callback(pBuf, buf_size, pBytes_actually_read, pCallback_data);
return 0;
}
uint8_t JPEGDecoder::pjpeg_need_bytes_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) {
uint n;
pCallback_data;
n = jpg_min(g_nInFileSize - g_nInFileOfs, buf_size);
if (jpg_source == JPEG_ARRAY) { // We are handling an array
for (int i = 0; i < n; i++) {
pBuf[i] = pgm_read_byte(jpg_data++);
//Serial.println(pBuf[i],HEX);
}
}
#ifdef LOAD_SPIFFS
if (jpg_source == JPEG_FS_FILE) g_pInFileFs.read(pBuf,n); // else we are handling a file
#endif
#ifdef LOAD_SD_LIBRARY
if (jpg_source == JPEG_SD_FILE) g_pInFileSd.read(pBuf,n); // else we are handling a file
#endif
*pBytes_actually_read = (uint8_t)(n);
g_nInFileOfs += n;
return 0;
}
int JPEGDecoder::decode_mcu(void) {
status = pjpeg_decode_mcu();
if (status) {
is_available = 0 ;
if (status != PJPG_NO_MORE_BLOCKS) {
#ifdef DEBUG
Serial.print("pjpeg_decode_mcu() failed with status ");
Serial.println(status);
#endif
return -1;
}
}
return 1;
}
int JPEGDecoder::read(void) {
int y, x;
uint16_t *pDst_row;
if(is_available == 0 || mcu_y >= image_info.m_MCUSPerCol) {
abort();
return 0;
}
// Copy MCU's pixel blocks into the destination bitmap.
pDst_row = pImage;
for (y = 0; y < image_info.m_MCUHeight; y += 8) {
const int by_limit = jpg_min(8, image_info.m_height - (mcu_y * image_info.m_MCUHeight + y));
for (x = 0; x < image_info.m_MCUWidth; x += 8) {
uint16_t *pDst_block = pDst_row + x;
// Compute source byte offset of the block in the decoder's MCU buffer.
uint src_ofs = (x * 8U) + (y * 16U);
const uint8_t *pSrcR = image_info.m_pMCUBufR + src_ofs;
const uint8_t *pSrcG = image_info.m_pMCUBufG + src_ofs;
const uint8_t *pSrcB = image_info.m_pMCUBufB + src_ofs;
const int bx_limit = jpg_min(8, image_info.m_width - (mcu_x * image_info.m_MCUWidth + x));
if (image_info.m_scanType == PJPG_GRAYSCALE) {
int bx, by;
for (by = 0; by < by_limit; by++) {
uint16_t *pDst = pDst_block;
for (bx = 0; bx < bx_limit; bx++) {
#ifdef SWAP_BYTES
*pDst++ = (*pSrcR & 0xF8) | (*pSrcR & 0xE0) >> 5 | (*pSrcR & 0xF8) << 5 | (*pSrcR & 0x1C) << 11;
#else
*pDst++ = (*pSrcR & 0xF8) << 8 | (*pSrcR & 0xFC) <<3 | *pSrcR >> 3;
#endif
pSrcR++;
}
pDst_block += row_pitch;
}
}
else {
int bx, by;
for (by = 0; by < by_limit; by++) {
uint16_t *pDst = pDst_block;
for (bx = 0; bx < bx_limit; bx++) {
#ifdef SWAP_BYTES
*pDst++ = (*pSrcR & 0xF8) | (*pSrcG & 0xE0) >> 5 | (*pSrcB & 0xF8) << 5 | (*pSrcG & 0x1C) << 11;
#else
*pDst++ = (*pSrcR & 0xF8) << 8 | (*pSrcG & 0xFC) <<3 | *pSrcB >> 3;
#endif
pSrcR++; pSrcG++; pSrcB++;
}
pSrcR += (8 - bx_limit);
pSrcG += (8 - bx_limit);
pSrcB += (8 - bx_limit);
pDst_block += row_pitch;
}
}
}
pDst_row += (row_pitch * 8);
}
MCUx = mcu_x;
MCUy = mcu_y;
mcu_x++;
if (mcu_x == image_info.m_MCUSPerRow) {
mcu_x = 0;
mcu_y++;
}
if(decode_mcu()==-1) is_available = 0 ;
return 1;
}
int JPEGDecoder::readSwappedBytes(void) {
int y, x;
uint16_t *pDst_row;
if(is_available == 0 || mcu_y >= image_info.m_MCUSPerCol) {
abort();
return 0;
}
// Copy MCU's pixel blocks into the destination bitmap.
pDst_row = pImage;
for (y = 0; y < image_info.m_MCUHeight; y += 8) {
const int by_limit = jpg_min(8, image_info.m_height - (mcu_y * image_info.m_MCUHeight + y));
for (x = 0; x < image_info.m_MCUWidth; x += 8) {
uint16_t *pDst_block = pDst_row + x;
// Compute source byte offset of the block in the decoder's MCU buffer.
uint src_ofs = (x * 8U) + (y * 16U);
const uint8_t *pSrcR = image_info.m_pMCUBufR + src_ofs;
const uint8_t *pSrcG = image_info.m_pMCUBufG + src_ofs;
const uint8_t *pSrcB = image_info.m_pMCUBufB + src_ofs;
const int bx_limit = jpg_min(8, image_info.m_width - (mcu_x * image_info.m_MCUWidth + x));
if (image_info.m_scanType == PJPG_GRAYSCALE) {
int bx, by;
for (by = 0; by < by_limit; by++) {
uint16_t *pDst = pDst_block;
for (bx = 0; bx < bx_limit; bx++) {
*pDst++ = (*pSrcR & 0xF8) | (*pSrcR & 0xE0) >> 5 | (*pSrcR & 0xF8) << 5 | (*pSrcR & 0x1C) << 11;
pSrcR++;
}
}
}
else {
int bx, by;
for (by = 0; by < by_limit; by++) {
uint16_t *pDst = pDst_block;
for (bx = 0; bx < bx_limit; bx++) {
*pDst++ = (*pSrcR & 0xF8) | (*pSrcG & 0xE0) >> 5 | (*pSrcB & 0xF8) << 5 | (*pSrcG & 0x1C) << 11;
pSrcR++; pSrcG++; pSrcB++;
}
pSrcR += (8 - bx_limit);
pSrcG += (8 - bx_limit);
pSrcB += (8 - bx_limit);
pDst_block += row_pitch;
}
}
}
pDst_row += (row_pitch * 8);
}
MCUx = mcu_x;
MCUy = mcu_y;
mcu_x++;
if (mcu_x == image_info.m_MCUSPerRow) {
mcu_x = 0;
mcu_y++;
}
if(decode_mcu()==-1) is_available = 0 ;
return 1;
}
// Generic file call for SD or SPIFFS, uses leading / to distinguish SPIFFS files
int JPEGDecoder::decodeFile(const char *pFilename){
#ifdef ESP8266
#ifdef LOAD_SD_LIBRARY
if (*pFilename == '/')
#endif
return decodeFsFile(pFilename);
#endif
#ifdef LOAD_SD_LIBRARY
return decodeSdFile(pFilename);
#endif
return -1;
}
int JPEGDecoder::decodeFile(const String& pFilename){
#ifdef ESP8266
#ifdef LOAD_SD_LIBRARY
if (pFilename.charAt(0) == '/')
#endif
return decodeFsFile(pFilename);
#endif
#ifdef LOAD_SD_LIBRARY
return decodeSdFile(pFilename);
#endif
return -1;
}
#ifdef LOAD_SPIFFS
// Call specific to SPIFFS
int JPEGDecoder::decodeFsFile(const char *pFilename) {
fs::File pInFile = SPIFFS.open( pFilename, "r");
return decodeFsFile(pInFile);
}
int JPEGDecoder::decodeFsFile(const String& pFilename) {
fs::File pInFile = SPIFFS.open( pFilename, "r");
return decodeFsFile(pInFile);
}
int JPEGDecoder::decodeFsFile(fs::File jpgFile) { // This is for the SPIFFS library
g_pInFileFs = jpgFile;
jpg_source = JPEG_FS_FILE; // Flag to indicate a SPIFFS file
if (!g_pInFileFs) {
#ifdef DEBUG
Serial.println("ERROR: SPIFFS file not found!");
#endif
return -1;
}
g_nInFileOfs = 0;
g_nInFileSize = g_pInFileFs.size();
return decodeCommon();
}
#endif
#ifdef LOAD_SD_LIBRARY
// Call specific to SD filing system in case leading / is used
int JPEGDecoder::decodeSdFile(const char *pFilename) {
File pInFile = SD.open( pFilename, FILE_READ);
return decodeSdFile(pInFile);
}
int JPEGDecoder::decodeSdFile(const String& pFilename) {
File pInFile = SD.open( pFilename, FILE_READ);
return decodeSdFile(pInFile);
}
int JPEGDecoder::decodeSdFile(File jpgFile) { // This is for the SD library
g_pInFileSd = jpgFile;
jpg_source = JPEG_SD_FILE; // Flag to indicate a SD file
if (!g_pInFileSd) {
#ifdef DEBUG
Serial.println("ERROR: SD file not found!");
#endif
return -1;
}
g_nInFileOfs = 0;
g_nInFileSize = g_pInFileSd.size();
return decodeCommon();
}
#endif
int JPEGDecoder::decodeArray(const uint8_t array[], uint32_t array_size) {
jpg_source = JPEG_ARRAY; // We are not processing a file, use arrays
g_nInFileOfs = 0;
jpg_data = (uint8_t *)array;
g_nInFileSize = array_size;
return decodeCommon();
}
int JPEGDecoder::decodeCommon(void) {
status = pjpeg_decode_init(&image_info, pjpeg_callback, NULL, 0);
if (status) {
#ifdef DEBUG
Serial.print("pjpeg_decode_init() failed with status ");
Serial.println(status);
if (status == PJPG_UNSUPPORTED_MODE) {
Serial.println("Progressive JPEG files are not supported.");
}
#endif
return -1;
}
decoded_width = image_info.m_width;
decoded_height = image_info.m_height;
row_pitch = image_info.m_MCUWidth;
pImage = new uint16_t[image_info.m_MCUWidth * image_info.m_MCUHeight];
if (!pImage) {
#ifdef DEBUG
Serial.println("Memory Allocation Failure");
#endif
return -1;
}
memset(pImage , 0 , sizeof(pImage));
row_blocks_per_mcu = image_info.m_MCUWidth >> 3;
col_blocks_per_mcu = image_info.m_MCUHeight >> 3;
is_available = 1 ;
width = decoded_width;
height = decoded_height;
comps = 1;
MCUSPerRow = image_info.m_MCUSPerRow;
MCUSPerCol = image_info.m_MCUSPerCol;
scanType = image_info.m_scanType;
MCUWidth = image_info.m_MCUWidth;
MCUHeight = image_info.m_MCUHeight;
return decode_mcu();
}
void JPEGDecoder::abort(void) {
mcu_x = 0 ;
mcu_y = 0 ;
is_available = 0;
if(pImage) delete pImage;
pImage = NULL;
#ifdef LOAD_SPIFFS
if (jpg_source == JPEG_FS_FILE) if (g_pInFileFs) g_pInFileFs.close();
#endif
#ifdef LOAD_SD_LIBRARY
if (jpg_source == JPEG_SD_FILE) if (g_pInFileSd) g_pInFileSd.close();
#endif
}