You can make a pretty neat low-resolution display out of LEDs arranged in rows and columns — you can even buy ready matrices that save you a lot of work with it. You "only" need one GPIO pin per every row and every column (you can save a lot of them by charlieplexing). The way you display things is that you enable one row (or column) at a time, and enable the columns (or rows) for that set of pixels, and then you disable it and switch to the next row (or column). If you do that quick enough, the LEDs won't even have time to stop shining, and you will have a pretty reasonable image displayed. But there are several problems with this.
First, you still need resistors on your LEDs. Second, because the number of LEDs that are on in a given row changes, so does the current taken by them, and since your resistor doesn't change, the row gets fainter the more pixels are shining on it. Third, you need your microcontroller to keep scanning the rows of the matrix and re-displaying the pixels all the time. Fourth, if you have too many rows, the total time each of them is on becomes rather short, and thus they become too faint. Fifth, if you want to have brightness control for the individual pixels, you need to scan the matrix even faster — the more different levels of brightness you need, the faster. Sixth, if your microcontroller does anything else at the same time, you might see some artifacts due to the timings not being exactly right. And finally, one pin per row and column is a lot of pins.
So can you do better? Of course!
First of all, you don't have to use GPIO pins to drive LEDs directly. There are mosfets, there are logic buffers, and finally, the best of them, there are constant-current drivers — those will let you get rid of the resistors and fix the varying row brightness problem! But what about the number of pins? You get a whole plethora of pin multiplexers, decade counters and shift registers you could use for that. What about the requirements on speed constant scanning? Well you could use a dedicated microcontroller that you just tell which pixels should be on or off once, and that will do the scanning for you.
All of those problems have their solutions, but all of them will add more and more components to your project's bill of materials, and also combining them all together and making sure they all fit together is a bit of a pain. Surely there must be integrated circuits that already contain all those required components inside a single package? You bet there are!
I know about roughly four families of chips dedicated to driving LED matrices (and also 7-segment displays and similar things). They vary in size, price, features and ease of use. They all have their own internal memory and some kind of a communication bus through which you can modify it, and they keep displaying their contest without you having to do anything more, however, the exact details tend to vary as well. I want to list the ones I know here, and if you know of any more, please feel free to leave a comment.
One of the most widespread chips of this kind is the venerable MAX7219. You can still buy ready matrix modules with it. It can drive up to 8x8 pixels, switching them on or off (1 bit) and changing their global brightness. You control it over SPI and it also has a second SPI bus to which you can forward commands, so you can chain several of those chips on one bus this way. They also come in huge DIP and SO packages, which makes them perfect for breadboard use. It requires one extra component — the resistor for setting/measuring the current.
But they are not the only LED matrix drivers that Maxim makes. In fact, there is a whole big list of them. Unfortunately I didn't really have any opportunity to use any of those, so I won't be writing too much about them.
The second most popular chip for this that I know is the HT16K33. It's also popular in matrix modules, I think due to Adafruit using it in their matrix "backpacks". Depending on the package it can drive up to 8x8 or 16x8 pixels, switching them on or off (1 bit), and changing their global brightness (4 bit) and global blinking speed (4 bit). As a bonus, you can also make it scan a matrix of up to 13x3 buttons at the same time as it drives your display. You control it over I2C (TWI), and you can select the address, so you can have up to 8 (depending on the package) of them on the same bus. It comes in rather bulky SOP20, SOP24 and SOP28 packages easy to solder by hand. It the simplest use case it doesn't require additional components, other than the obligatory pull-up resistors on the I2C bus lines, but if you need to set a different address or scan the buttons, then you will need some extra diodes and resistors.
There are currently two more similar chips sold by Holtek, and three more advanced ones, as you can see on the list. Again, I never really had any experience with those, so I will not talk about them here.
Titan Micro Electronics
Let us look at some of the cheaper alternatives, such as the TM1640 that is being used in the official matrix shield for the D1 Mini boards and also is used in some bi-color matrix modules available out there. It can drive up to 16x8 pixels, switching them on or off (1 bit). You control it over some weird serial protocol that is a mixture of SPI and I2C that requires only two pins, but is not addressable, so you will need separate pins for every device. It comes in the same bulku SOP28 package as the previous chip, but it's not pin-compatible. It doesn't need additional components, apart from the pull-up resistors on the signal lines.
That company makes a lot of other LED driving chips, and there are also a lot of similarly named cheap clones available on the market, but unfortunately most of them use some custom non-standard serial protocol for control, and the datasheets come in Chinese, so they take some work to use.
Integrated Silicon Solution
The ISSI has a number of interesting chips in this category. I'm going to describe two of them, but you can also look at the more elaborate experiments I did with them.
The IS31FL3728 (pdf) can drive up to 8x8 (it has several modes, up to 11x5) pixels switching them on or off (1 bit) with global brightness (4 bit). It also has an analog input (audio) that can be used to modulate the global brightness or to display an "equalizer" bar graph of frequencies. You control it over I2C (TWI) with up to 4 different addresses. It comes in a tiny QFN-24 package, so it's nice for the projects where you don't have much space. At the minimum, it requires one resistor and one capacitor apart from the usual pull-up resistors on the bus lines.
The IS31FL3733 (pdf) can drive up to 16x12 pixels with 256 (8 bit) individual brightness levels and 256 (8 bit) levels of global brightness. You control it over I2C (TWI) and you can set 4 different addresses. It comes in QFN48 or eTQFP48 package. Apart from the pull-up resistors on the bus lines it also requires a resistor for setting the current.
This company makes many more very interesting LED-driving chips, included ones for charlie-plexed displays. The two I described seem the most interesting to me.