Skip to content
Larry Bank edited this page Jun 16, 2024 · 8 revisions

Public API

The JPEGDEC class exposes a few simple 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 JPEGDEC:

class JPEGDEC {
  public:
    int openRAM(uint8_t *pData, int iDataSize, JPEG_DRAW_CALLBACK *pfnDraw);
    int openFLASH(uint8_t *pData, int iDataSize, JPEG_DRAW_CALLBACK *pfnDraw);
    int open(char *szFilename, JPEG_OPEN_CALLBACK *pfnOpen, JPEG_CLOSE_CALLBACK *pfnClose, JPEG_READ_CALLBACK *pfnRead, JPEG_SEEK_CALLBACK *pfnSeek, JPEG_DRAW_CALLBACK *pfnDraw);
    void close();
    int decode(int x_offset, int y_offset, int option_bits);
    int decodeDither(uint8_t *pDither, int iOptions);
    int getWidth();
    int getHeight();
    int getBpp();
    int getSubSample();
    int hasThumb();
    int getThumbWidth();
    int getThumbHeight();
    int getLastError();
    void setPixelType(int iPixelType); // defaults to RGB565 little-endian
    void setMaxOutputSize(int iMCUs);
    void setCropArea(int x, int y, int w, int h); 
    void getCropArea(int *x, int *y, int *w, int *h);

  private:
    JPEGIMAGE _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:

open()
There are three versions of the open method - two for images in memory (RAM or FLASH) and another for images coming from a file. Both return 1 for success, 0 for failure. The success or failure depends not just on the file successfully opening, but also on the JPEG image file being parsed a little to gather the size and other header info. Once this function returns successfully, you can use getWidth() and getHeight().

close()
You can call this any time to close the file handle associated with your image. For files coming from memory, this has no effect.

decode(x, y, options)
This method decodes the current image and returns 1 if it succeeds. The x,y offset allows you to specify where on the target display the image is drawn (upper left corner). The following options can be used (can be combined):
JPEG_SCALE_HALF - decode the image at 1/2 size
JPEG_SCALE_QUARTER - decode the image at 1/4 size
JPEG_SCALE_EIGHTH - decode the image at 1/8 size
JPEG_EXIF_THUMBNAIL - decode the thumbnail, not the main image

decodeDither(uint8_t *pDither, int iOptions)
This method decodes the current image and returns 1 if it succeeds. The output type must be set to one of the 3 dithered options. Since this requires more memory than is available in the default JPEGIMAGE structure, you must provide your own RAM pointer which must hold at least width x 16 bytes. For example, if your image is 640x480 and you're decoding it at half-size, you must provide a buffer that is 320x16 = 5120 bytes.

getWidth()
Returns the image width in pixels. This can be called any time after a successful open().

getWidth()
Returns the height in pixels.

hasThumb()
This method returns true/1 if the image contains an Exif thumbnail.

getThumbWidth()
Returns the thumbnail width in pixels (if present).

getThumbHeight()
Returns the thumbnail height in pixels (if present).

getLastError()
Returns the last error or JPEG_SUCCESS of none.

setPixelType(int iPixelType)
Can be one of RGB565_LITTLE_ENDIAN (the default), RGB565_BIG_ENDIAN, EIGHT_BIT_GRAYSCALE, FOUR_BIT_DITHERED, TWO_BIT_DITHERED, or ONE_BIT_DITHERED. This MUST be called after open() or it will be reset to the default. If you choose the 8-bit grayscale option, all images will return grayscale pixels (color and grayscale). The dither options require the use of the decodeDither() method instead of the decode() method

setMaxOutputSize(int iMaxMCUs)
JPEGDEC will try to draw as many pixels as possible in each callback of the draw function. An internal buffer holds up to 2048 pixels per call. If you would like fewer pixels draw per call, you can set this value to the maximum number of MCUs (minimum coded units) that will be passed in each draw call. For example, by default when decoding a 4:2:0 subsampled image, each MCU is 16x16 pixels, so each draw call will have up to 8 MCUs arranged as 128x16 pixel blocks

setCropArea(int x, int y, int w, int h)
This method allows you to tell JPEGDEC to only decode a smaller portion of the image (along MCU boundaries). The output sent to the JPEGDraw callback will only contain the pixels you've specified in the crop area. The decoding will go proportionally faster because the undesired pixels do not need to be processed.

getCropArea(int *x, int *y, int *w, int *h)
This method returns the adjusted crop area. The crop area must be along MCU boundaries, so an area requested with setCropArea() may be adjusted to fit on MCU boundaries. The rectangle will be enlarged if partial MCUs are specified.

The Callback Functions

There are 5 callback functions defined by JPEGDEC. If you're displaying an image from memory, then you only need to provide a single function - JPEG_DRAW_CALLBACK because the memory 'file' functions are provided for you inside the library code. The example sketches contain code which implements this sufficiently to display most JPEGs properly on any LCD. Let's see what's involved in implementing it. Here's the function prototype:

typedef void (JPEG_DRAW_CALLBACK)(JPEGDRAW *pDraw);

The JPEGDRAW structure is defined like this:

typedef struct jpeg_draw_tag
{
  int x, y; // corner offset of this block of pixels
  int iWidth, iHeight; // size of this pixel block
  int iBpp; // bit depth of the pixels (8 or 16)
  uint16_t *pPixels; // RGB565 pixels
  void *pUser;
} JPEGDRAW;

Hopefully you find the comments for each member variable pretty clear. The JPEGDRAW callback is tasked with drawing the current block of pixels on the display. It's provided with the RGB565 pixel data arranged in a width x height block. The code tries by default to send as many pixels as possible per call. The only way to work efficiently on serial displays is to write as many pixels as possible in each transaction. It also helps if your MCU can use DMA to write those pixels so that it doesn't have to wait for the each write transaction to finish. The examples which use my bb_spi_lcd library make use of DMA on a few systems. The code to manage DMA transactions hasn't been abstracted by the Arduino API, so it must be written uniquely for each MCU. Since we're trying to use as little RAM as possible on the target MCU, we need to make use of the memory built into the LCD controller.

The other 4 callback functions need to be implemented if you're working with files. They implement the standard functions of open, close, read, and seek:

typedef void * (JPEG_OPEN_CALLBACK)(char *szFilename, int32_t *pFileSize);
typedef void (JPEG_CLOSE_CALLBACK)(void *pHandle);
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 JPEGDEC 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(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  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 any of the sdcard example sketches for how to implement the other callback functions.

Clone this wiki locally