Charlieplexing is fun!
Your implementation already displays a nice visual way to get around with that.
It could be convenient to transform these individual addresses to x/y coordinates... Maybe just splitting the bits of the address would suffice since you have 8 pixel per row.
Focusing on the first row, how to get the right pins Low/High/High-Z for each position?
There might be something to do with the "<<" operator, to shift a bit by as much as a binary number tells us.
With that we can already make the first row!
Due to how charlieplexing work, and as you figured out, there is the need to skip a position while row == column, as well as everything after that.
That looks like about trying to build-up the bitshift, then adjusting the shift amount when (row >= columns).
P.S.: I might have mistaken rows for columns at a few places!