mirror of
https://github.com/Bodmer/JPEGDecoder.git
synced 2025-08-06 14:22:16 +08:00
465 lines
12 KiB
C++
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
|
|
}
|
|
|
|
|