API - bitbank2/JPEGENC GitHub Wiki
The JPEGEnc class exposes a few methods detailed below. The more challenging part of using it is when you're using a new type of media/file and/or display and need to write your own callback functions. Those are documented below the API section.
This is the entire class definition for JPEGEnc:
class JPEGEnc
{
public:
int open(const char *szFilename, JPEGE_OPEN_CALLBACK *pfnOpen, JPEGE_CLOSE_CALLBACK *pfnClose, JPEGE_READ_CALLBACK *pfnRead, JPEGE_WRITE_CALLBACK *pfnWrite, JPEGE_SEEK_CALLBACK *pfnSeek);
int open(uint8_t *pOutput, int iBufferSize);
int close();
int encodeBegin(JPEGENCODE *pEncode, int iWidth, int iHeight, uint8_t ucPixelType, uint8_t ucSubSample, uint8_t ucQFactor);
int addMCU(JPEGENCODE *pEncode, uint8_t *pPixels, int iPitch);
int addFrame(JPEGENCODE *pEncode, uint8_t *pPixels, int iPitch);
int getLastError();
private:
JPEGE_IMAGE _jpeg;
};
The class is basically a C++ wrapper of C code which does all of the actual work. The JPEGIMAGE structure is kept private, but is passed as a pointer to the 'worker' functions. This makes it easy to lift out the C code if you want to use it in a pure C project. Here are details of each method:
int open()
There are two versions of the open method - one for images to compress to RAM and another for incrementally writing the output to a file. All return 0 (JPEGE_SUCCESS) for success, non-0 for failure (see error codes). The success or failure depends on the file successfully opening in write mode. Once this function returns successfully, you can set the image parameters with encodeBegin()
and then start feeding it image data with addMCU()
or addFrame()
.
int close()
Once all of the image data has been passed with addMCU(), call this to finalize the compressed data and close the file (if using callbacks).
int encodeBegin(JPEGENCODE *pEncode, int iWidth, int iHeight, uint8_t ucPixelType, uint8_t ucSubSample, uint8_t ucQFactor)
This method passes the image parameters to the library and initializes the JPEGENCODE structure in preparation for calling addMCU()
. Here are details of the parameters:
iWidth - image width in pixels
iHeight - image height in pixels
ucPixelType - one of JPEG_PIXEL_GRAYSCALE, JPEG_PIXEL_RGB565, JPEG_PIXEL_RGB888, or JPEG_PIXEL_ARGB8888
ucSubSample - one of JPEG_SUBSAMPLE_444 (none) or JPEG_SUBSAMPLE_420 (2:1 color subsampling)
ucQFactor - the quality level has 4 options: JPEG_Q_BEST, JPEG_Q_HIGH, JPEG_Q_MED and JPEG_Q_LOW
int addMCU(JPEGENCODE *pEncode, uint8_t *pPixels, int iPitch)
The image is encoded in what are called "Minimum Coded Units" from the top left down to the bottom right. I decided to make it a "push" design instead of pull. This way you can prepare the image data on the fly and push it to the encoder when you're ready. A common use case will be to have a full image ready to encode and just call addMCU()
in a loop, passing it a pointer to the current MCU pixels for each call. Below is an example:
rc = jpg.encodeBegin(&jpe, iWidth, iHeight, ucPixelType, JPEG_SUBSAMPLE_420, JPEG_QUALITY_BEST);
if (rc == JPEG_SUCCESS) {
iMCUCount = ((iWidth + jpe.cx-1)/ jpe.cx) * ((iHeight + jpe.cy-1) / jpe.cy);
for (i=0; i<iMCUCount && rc == JPEG_SUCCESS; i++) {
rc = jpg.addMCU(&jpe, &pBitmap[(jpe.x * iBytePP) + (jpe.y * iPitch)], iPitch);
}
iDataSize = jpg.close();
}
The image pointer is calculated to be the upper left corner of each MCU, along with the pitch (bytes per line) of the source image. The JPEGENCODE structure contains the MCU size (8x8 or 16x16 if subsampling the color) and the current x,y of the MCU to be encoded. This x,y coordinate is updated by the addMCU()
function each time you call it. The return value should be JPEG_SUCCESS for each call unless you try to go past the end of the image (call it too many times) or run out of memory (if using an output buffer instead of file I/O).
int addFrame(JPEGENCODE *pEncode, uint8_t *pPixels, int iPitch)
If you have the entire frame in memory, JPEGEnc can compress it with a single function call. The iPitch parameter is the number of bytes per line of source image
int getLastError()
Returns the last error that occurred.
There are 5 callback functions defined by JPEGENC. If you're encoding a JPEG image to memory and providing a buffer which can hold the entire compressed data, then you don't need to provide any callback functions. Let's see what's involved in implementing them. Here are the function prototypes:
typedef void * (JPEG_OPEN_CALLBACK)(char *szFilename, int32_t *pFileSize);
typedef void (JPEG_CLOSE_CALLBACK)(JPEGFILE *pFile);
typedef int32_t (JPEG_WRITE_CALLBACK)(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen);
typedef int32_t (JPEG_READ_CALLBACK)(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen);
typedef int32_t (JPEG_SEEK_CALLBACK)(JPEGFILE *pFile, int32_t iPosition);
The challenge with the file system callbacks is that file access on Arduino is usually not associated with a simple file handle, but with a C++ class. In order to manage this in a generic way that will work for all possible systems, the JPEGENC class holds onto a void *
pointer which you would like use to hold a class pointer. Let's look at the JPEG_CLOSE_CALLBACK function I wrote for the Arduino SD library to understand how this is done:
void JPEGCloseFile(JPEGFILE *pFile)
{
File *f = static_cast<File *>(pFile->fHandle);
if (f != NULL)
f->close();
}
The trick to making it all work is just to use static_cast
to convert the void *
into whatever class you need to access files.
See the encode_jpg_to_sd example sketch for how to implement the other callback functions.