An extension to UI.NCurses for drawing half-height console graphics.
By drawing a Unicode Upper Half Block character (▀) with different foreground and background colors, one can simulate drawing two rows of a 2D grid of colors in one row, achieving grid cells that take up half a console row alongside regular console text.
There's a brief writeup on my blog.
The main caveat is that one is now restricted to 15 unique colors: UI.NCurses usually lets you define 255 custom foreground/background combinations, but now we might have any two colors appear as our foreground/background. 16 colors would tip us over, requiring 256 possible pairs, so we are limited to 15 colors and 225 pairings.
greyScale :: [String]
greyScale =
[ "000000",
"111111",
-- ... 12 intermediate hex strings omitted ...
"ffffff"
]
-- Calculate the Mandelbrot steps-to-divergence and plot as Color 1 to Color 15
mandelbrot :: (Fractional a, RealFloat a) => a -> a -> Color
mandelbrot x y = Color (16 - ((+) 1 $ steps (0 :+ 0) (x' :+ y') 0))
where
x' = (x - 100) * 0.025
y' = (y - 50) * 0.025
steps z c i
| i > 13 || magnitude z' > 2 = i
| otherwise = steps z' c (i + 1)
where
z' = z * z + c
-- Draw the Mandelbrot set to the buffer point by point
drawMandelbrot :: Buffer -> Buffer
drawMandelbrot buffer =
foldl'
(\b (x, y) -> setXY x y (mandelbrot (fromIntegral x) (fromIntegral y)) b)
buffer
[(x, y) | x <- [0 .. 149], y <- [0 .. 99]]
main :: IO ()
main = do
-- Register the 15 greyscale hex colors as Color 1 through Color 15.
-- Every combination of these colors (fg and bg) is registered with Curses
-- with a unique ID, which we return in the colorMap.
colorMap <- runCurses $ initHexColors greyScale
-- Creates a new buffer and draws the Mandelbrot set in each cell using the
-- greyscale colors 1 through 15
let buffer = drawMandelbrot $ mkBuffer 150 100 (Color 1)
runCurses $ do
-- Hide the cursor
setEcho False
setCursorMode CursorInvisible
-- Enter an infinite drawing loop displaying the buffer.
forever $ do
let drawOp = do
-- First draw the buffer to the screen.
drawBuffer colorMap 0 0 buffer
-- Now draw some text in the centre.
-- We set the color to curses ID corresponding to fg 15 (white) and bg 1 (black)
let (Just c) = colorId colorMap (Color 15) (Color 1)
setColor c
moveCursor 25 50
drawText "| Text demonstrating that each cell is half the row height |"
w <- defaultWindow
updateWindow w drawOp
renderIn this example we:
- Register
Color 1throughColor 15by providing a list of 15 hex strings toinitHexColors. This sets the terminal colors appropriately, registers a unique cursesColorIDfor every possible foreground/background combination of each of the 15 colors, and returns a map from (fg, bg) tuples to correspondingColorID. - Create a
Buffer(a 2DVectorofColor) by providing dimensions and a default color tomkBuffer. - Draw on this buffer using
SetXY. - When ready to blit to the screen,
drawBufferwill create the appropriate cursesUpdate ()operations for writing the corresponding Upper Half Block characters to the terminal to render the buffer rows at half-height.
