Adafruit GFX Graphics Library
Adafruit GFX Graphics Library
The Adafruit_GFX library for Arduino provides a common syntax and set of graphics functions for all of our
LCD and OLED displays. This allows Arduino sketches to easily be adapted between display types with
minimal fuss…and any new features, performance improvements and bug fixes will immediately apply
across our complete offering of color displays.
The Adafruit_GFX library can be installed using the Arduino Library Manager…this is the preferred and
modern way. From the Arduino “Sketch” menu, select “Include Library” then “Manage Libraries…”
While you’re there, also look for and install the Adafruit_BusIO library (or…newer Arduino IDE versions
The Adafruit_GFX library always works together with an additional library unique to each specific display
type — for example, the ST7735 1.8" color LCD requires installing Adafruit_GFX, Adafruit_BusIO and the
Adafruit_ST7735 library. The following libraries now operate in this manner:
The libraries are written in C++ for Arduino but could easily be ported to any microcontroller by rewriting
the low-level pin access functions.
https://adafru.it/cBB
https://adafru.it/cBB
https://adafru.it/Ldl
https://adafru.it/Ldl
Also unlike the mathematical Cartesian coordinate system, points here have dimension — they are always
one full integer pixel wide and tall.
Coordinates are always expressed in pixel units; there is no implicit scale to a real-world measure like
millimeters or inches, and the size of a displayed graphic will be a function of that specific display’s dot
pitch or pixel density. If you’re aiming for a real-world dimension, you’ll need to scale your coordinates to
suit. Dot pitch can often be found in the device datasheet, or by measuring the screen width and dividing
the number of pixels across by this measurement.
For color-capable displays, colors are represented as unsigned 16-bit values. Some displays may
physically be capable of more or fewer bits than this, but the library operates with 16-bit values…these are
easy for the Arduino to work with while also providing a consistent data type across all the different
displays. The primary color components — red, green and blue — are all “packed” into a single 16-bit
variable, with the most significant 5 bits conveying red, middle 6 bits conveying green, and least
significant 5 bits conveying blue. That extra bit is assigned to green because our eyes are most sensitive
to green light. Science!
For the most common primary and secondary colors, we have this handy cheat-sheet that you can include
// Color definitions
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
For monochrome (single-color) displays, colors are always specified as simply 1 (set) or 0 (clear). The
semantics of set/clear are specific to the type of display: with something like a luminous OLED display, a
“set” pixel is lighted, whereas with a reflective LCD display, a “set” pixel is typically dark. There may be
exceptions, but generally you can count on 0 (clear) representing the default background state for a
freshly-initialized display, whatever that works out to be.
The function descriptions below are merely prototypes — there’s an assumption that a display object is
declared and initialized as needed by the device-specific library. Look at the example code with each
library to see it in actual use. For example, where we show print(1234.56), your actual code would place
the object name before this, e.g. it might read screen.print(1234.56) (if you have declared your display
object with the name screen).
Drawing lines
You can also draw lines, with a starting and end point and color:
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color);
Rectangles
Next up, rectangles and squares can be drawn and filled using the following procedures. Each accepts an
X, Y pair for the top-left corner of the rectangle, a width and height (in pixels), and a
color. drawRect() renders just the frame (outline) of the rectangle — the interior is unaffected —
while fillRect() fills the entire area with a given color:
Circles
Likewise, for circles, you can draw and fill. Each function accepts an X, Y pair for the center point, a radius
in pixels, and a color:
void drawRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);
void fillRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);
Triangles
With triangles, once again there are the draw and fill functions. Each requires a full seven parameters: the
X, Y coordinates for three corner points defining the triangle, followed by a color:
void drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
Text is very flexible but operates a bit differently. Instead of one procedure, the text size, color and
position are set up in separate functions and then the print() function is used — this makes it easy and
provides all of the same string and number formatting capabilities of the familiar Serial.print() function!
Begin with setCursor(x, y), which will place the top left corner of the text wherever you please. Initially this
is set to (0,0) (the top-left corner of the screen). Then set the text color with setTextColor(color) — by
default this is white. Text is normally drawn “clear” — the open parts of each character show the original
background contents, but if you want the text to block out what’s underneath, a background color can be
specified as an optional second parameter tosetTextColor(). Finally, setTextSize(size) will multiply the
scale of the text by a given integer factor. Below you can see scales of 1 (the default), 2 and 3. It appears
blocky at larger sizes because we only ship the library with a single simple font, to save space.
Note that the text background color is not supported for custom fonts. For these, you will need to
determine the text extents and explicitly draw a filled rectangle before drawing the text.
By default, long lines of text are set to automatically “wrap” back to the leftmost column. To override this
behavior (so text will run off the right side of the display — useful for scrolling marquee effects), use
setTextWrap(false). The normal wrapping behavior is restored with setTextWrap(true).
See the “Using Fonts (https://adafru.it/kAf)” page for additional text features in the latest GFX library.
Bitmaps
You can draw small monochrome (single color) bitmaps, good for sprites and other mini-animations or
icons:
This issues a contiguous block of bits to the display, where each '1' bit sets the corresponding pixel to
'color,' while each '0' bit is skipped. x, y is the top-left corner where the bitmap is drawn, w, h are the width
and height in pixels.
The bitmap data must be located in program memory using the PROGMEM directive. This is a somewhat
advanced function and beginners are best advised to come back to this later. For an introduction, see
the Arduino tutorial on PROGMEM usage (https://adafru.it/aMw).
We can only rotate 0, 90, 180 or 270 degrees - anything else is not possible in hardware and is too taxing
for an Arduino to calculate in software
The rotation parameter can be 0, 1, 2 or 3. For displays that are part of an Arduino shield, rotation value 0
sets the display to a portrait (tall) mode, with the USB jack at the top right. Rotation value 2 is also a
portrait mode, with the USB jack at the bottom left. Rotation 1 is landscape (wide) mode, with the USB jack
at the bottom right, while rotation 3 is also landscape, but with the USB jack at the top left.
For other displays, please try all 4 rotations to figure out how they end up rotating as the alignment will
vary depending on each display, in general the rotations move counter-clockwise
When rotating, the origin point (0,0) changes — the idea is that it should be arranged at the top-left of the
display for the other graphics functions to make consistent sense (and match all the function descriptions
above).
If you need to reference the size of the screen (which will change between portrait and landscape
modes), use width() and height().
uint16_t width();
uint16_t height();
Located inside the “Fonts” folder inside Adafruit_GFX, the included files (as of this writing) are:
FreeMono12pt7b.h FreeSansBoldOblique12pt7b.h
FreeMono18pt7b.h FreeSansBoldOblique18pt7b.h
FreeMono24pt7b.h FreeSansBoldOblique24pt7b.h
FreeMono9pt7b.h FreeSansBoldOblique9pt7b.h
FreeMonoBold12pt7b.h FreeSansOblique12pt7b.h
FreeMonoBold18pt7b.h FreeSansOblique18pt7b.h
FreeMonoBold24pt7b.h FreeSansOblique24pt7b.h
FreeMonoBold9pt7b.h FreeSansOblique9pt7b.h
FreeMonoBoldOblique12pt7b.h FreeSerif12pt7b.h
FreeMonoBoldOblique18pt7b.h FreeSerif18pt7b.h
FreeMonoBoldOblique24pt7b.h FreeSerif24pt7b.h
FreeMonoBoldOblique9pt7b.h FreeSerif9pt7b.h
FreeMonoOblique12pt7b.h FreeSerifBold12pt7b.h
FreeMonoOblique18pt7b.h FreeSerifBold18pt7b.h
FreeMonoOblique24pt7b.h FreeSerifBold24pt7b.h
FreeMonoOblique9pt7b.h FreeSerifBold9pt7b.h
FreeSans12pt7b.h FreeSerifBoldItalic12pt7b.h
FreeSans18pt7b.h FreeSerifBoldItalic18pt7b.h
FreeSans24pt7b.h FreeSerifBoldItalic24pt7b.h
FreeSans9pt7b.h FreeSerifBoldItalic9pt7b.h
FreeSansBold12pt7b.h FreeSerifItalic12pt7b.h
FreeSansBold18pt7b.h FreeSerifItalic18pt7b.h
FreeSansBold24pt7b.h FreeSerifItalic24pt7b.h
FreeSansBold9pt7b.h FreeSerifItalic9pt7b.h
Each filename starts with the face name (“FreeMono”, “FreeSerif”, etc.) followed by the style (“Bold”,
“Oblique”, none, etc.), font size in points (currently 9, 12, 18 and 24 point sizes are provided) and “7b” to
indicate that these contain 7-bit characters (ASCII codes “ ” through “~”); 8-bit fonts (supporting symbols
and/or international characters) are not yet provided but may come later.
Each font takes up a bit of program space; larger fonts typically require more room. This is a finite
resource (about 32K max on an Arduino Uno for font data and all of your sketch code ), so choose
carefully. Too big and the code will refuse to compile (or in some edge cases, may compile but then won’t
upload to the board). If this happens, use fewer or smaller fonts, or use the standard built-in font.
Inside these .h files are several data structures, including one main font structure which will usually have
the same name as the font file (minus the .h). To select a font for subsequent graphics operations, use the
setFont() function, passing the address of this structure, such as:
tft.setFont(&FreeMonoBoldOblique12pt7b);
Subsequent calls to tft.print() will now use this font. Most other attributes that previously worked with the
built-in font (color, size, etc.) work similarly here.
To return to the standard fixed-size font, call setFont(), passing either NULL or no arguments:
tft.setFont();
Some text attributes behave a little differently with these new fonts. Not wanting to break compatibility
with existing code, the “classic” font continues to behave as before.
For example, whereas the cursor position when printing with the classic font identified the top-left corner
of the character cell, with new fonts the cursor position indicates the baseline — the bottom-most row —
of subsequent text. Characters may vary in size and width, and don’t necessarily begin at the exact cursor
column (as in below, this character starts one pixel left of the cursor, but others may be on or to the right
of it).
When switching between built-in and custom fonts, the library will automatically shift the cursor position
up or down 6 pixels as needed to continue along the same baseline.
Use getTextBounds() to determine the smallest rectangle encompassing a string, erase the area
using fillRect(), then draw new text:
getTextBounds expects a string, a starting cursor X&Y position (the current cursor position will not be
altered), and addresses of two signed and two unsigned 16-bit integers. These last four values will then
contain the upper-left corner and the width & height of the area covered by this text — these can then be
passed directly as arguments to fillRect().
This will unfortunately “blink” the text when erasing and redrawing, but is unavoidable. The old scheme of
drawing background pixels in the same pass only creates a new set of problems.
or:
Create a GFXcanvax1 object (an offscreen bitmap) for a fixed-size area, draw custom text in there
and copy to the screen using drawBitmap().
// In code later:
canvas.println("I like cake");
tft.drawBitmap(x, y, canvas.getBuffer(), 128, 32, foreground, background); // Copy to screen
This will be flicker-free but requires more RAM (about 512 bytes for the 128x32 pixel canvas shown
above), so it’s not always practical on AVR boards with only 2K. Arduino Mega or any 32-bit board should
manage fine.
Building this tool requires the gcc compiler and FreeType (https://adafru.it/kAh) library. Most Linux
distributions include both by default. For others, you may need to install developer tools and download
and build FreeType from the source (https://adafru.it/kAi). Then edit the Makefile to match your setup
before invoking “make”.
fontconvert expects at least two arguments: a font filename (such as a scalable TrueType vector font) and
a size, in points (72 points = 1 inch; the code presumes a screen resolution similar to the Adafruit 2.8" TFT
displays). The output should be redirected to a .h file…you can call this whatever you like but I try to be
somewhat descriptive:
The GNU FreeFont files are not included in the library repository but are
easily downloaded (https://adafru.it/kAj). Or you can convert most any font you like.
The name assigned to the font structure within this file is based on the input filename and font size, not
the output. This is why I recommend using descriptive filenames incorporating the font base name, size,
and "7p". Then the .h filename and font structure name can match.
The resulting .h file can be copied to the Adafruit_GFX/Fonts folder, or you can import the file as a new
tab in your Arduino sketch using the Sketch→Add File… command.
If in the Fonts folder, use this syntax when #including the file:
#include <Fonts/myfont12pt7b.h>
#include "myfont12pt7b.h"
Loading .BMP images from an SD card (or the flash memory chip on Adafruit “Express” boards) is an
option for most of our color displays…though it’s not built into Adafruit_GFX and must be separately
installed.
The Adafruit_ImageReader library handles this task. It can be installed through the Arduino Library
Manager (Sketch→Include Library→Manage Libraries…). Enter “imageread” in the search field and the
library is easy to spot:
While you’re there, also look for the Adafruit_SPIFlash library and install it similarly.
There’s one more library required, but it can’t be installed through the Library Manager. The Adafruit fork
of the SdFat library needs to be downloaded as a .ZIP file, uncompressed and installed the old-school
Arduino library way (https://adafru.it/m3e).
https://adafru.it/Fml
https://adafru.it/Fml
There are several example sketches in the Adafruit_ImageReader/examples folder. It’s recommended
that you dissect these for ideas how to use the library in your own projects.
One of these lines may vary from one example to the next, depending which display hardware it’s written
to support. Above we see it being used with the Adafruit_ILI9341 display library required of certain shields,
FeatherWings or breakout boards. Others examples reference Adafruit_HX8357, Adafruit_ST7735, or
other color TFT or OLED display libraries…use the right one for the hardware you have.
Most of the examples can work from either an SD card, or the small flash storage drive that’s on certain
Adafruit “Express” boards. The code to initialize one or the other is a little different, and the examples
check whether USE_SD_CARD is #defined to select one method vs. the other. If you know for a fact that
your own project only needs to run on one type or the other, you really only need the corresponding
initialization.
For a flash filesystem, there are some special declarations made that help us locate the flash device on
different Express boards, then declare three globals:
Then…we declare a display object (called “tft” in most of the examples) the usual way…for example, with
the 2.8 inch TFT touch shield for Arduino, it’s:
That all takes place in the global variable section, even before the setup() function.
Now we need to do some work in setup(), and again it’s different for SD cards vs. flash filesystems…
This example is providing some very basic error handling…checking the return status of SD.begin() and
printing a message to the Serial Monitor if there’s a problem.
if(!flash.begin()) {
Serial.println(F("flash begin() failed"));
for(;;);
}
if(!filesys.begin(&flash)) {
Serial.println(F("filesys begin() failed"));
for(;;);
}
All other code is now the same regardless whether using an SD card or flash. That either/or setup
required some extra steps but it’s all smooth sailing now…
After the SD (or flash) and TFT’s begin() functions have been called, you can then call
reader.drawBMP() to load a BMP image from the card to the screen:
ImageReturnCode stat;
stat = reader.drawBMP("/purple.bmp", tft, 0, 0);
A filename in “8.3” format (you shouldn’t need to provide an absolute path (the leading “/”), but there
are some issues with the SD library on some cutting-edge boards like the ESP32, so go ahead and
include this for good measure).
The display object where the image will be drawn (e.g. “tft”). This is the weird syntax previously
mentioned…rather than tft.drawBMP(), it’s reader.drawBMP(tft), because reasons.
An X and Y coordinate where the top-left corner of the image is positioned (this doesn’t need to be
within screen bounds…the library will clip the image as it’s loaded). 0, 0 will draw the image at the
This function returns a value of type ImageReturnCode , which you can either ignore or use it to provide
some diagnostic functionality. Possible values are:
IMAGE_SUCCESS — Image loaded successfully (or was clipped fully off screen, still considered
“successful” in that there was no error).
IMAGE_ERR_FILE_NOT_FOUND — Could not open the requested file (check spelling, confirm file
actually exists on the card, make sure it conforms to “8.3” file naming convention (e.g.
“filename.bmp”).
IMAGE_ERR_FORMAT — Not a supported image format. Currently only uncompressed 24-bit color
BMPs are supported (more will likely be added over time).
IMAGE_ERR_MALLOC — Could not allocate memory for operation (drawBMP() won’t generate this
error, but other ImageReader functions might).
Rather than dealing with these values yourself, you can optionally call a function to display a basic
diagnostic message to the Serial console:
reader.printStatus(stat);
If you need to know the size of a BMP image without actually loading it, there’s the bmpDimensions()
function:
This function returns an ImageReturnCode as explained with the drawBMP() function above.
This introduces another ImageReader function plus a new object type, Adafruit_Image :
Adafruit_Image img;
stat = reader.loadBMP("/wales.bmp", img);
The loadBMP() function is useful only on microcontrollers with considerable RAM, like the Adafruit “M0”
and “M4” boards, or ESP32. Small devices like the Arduino Uno just can’t cut it. It might be marginally
useful on the Arduino Mega with very small images.
After loading, use the img.draw() function to display an image on the screen:
img.draw(tft, x, y);
A display object (e.g. “tft” in most of the examples), similar to how drawBMP() worked.
An X and Y coordinate for the upper-left corner of the image on the screen, again similar to
drawBMP() .
We use img.draw(tft,…) rather than tft.drawRGBBitmap(…) (or other bitmap-drawing functions in the
Adafruit_GFX library) because in the future we plan to add more flexibility with regard to image file
formats and types. The Adafruit_Image object “understands” a bit about the image that’s been loaded
and will call the appropriate bitmap-rendering function automatically, you won’t have to handle each
separate case on your own.
If the image failed to load for any reason, img.draw() can still be called, it just won’t do anything. But at
least the sketch won’t crash.