YES! I have developed an ingenious solution for this. Here’s how:
- Draw a bunch of random green and blue blocks in the terminal using Unicode Full Block characters (‘█’).
- Make one of these blocks red.
- Flush the stdout.
- Short sleep, this is important otherwise it won’t work (0.1 seconds seems enough).
- Now capture the screen. On Windows this is performed with ccalls to Windows GDI.
- Now we will search the screen for anything that’s red. Note: There may be false positives.
- For any potentially matching red pixels, expand the area to find the size of that block.
- Once you’ve found the size, search all around the red block to see if the surroundings exactly match the random green and blue blocks that you previously drew.
- Once everything matches, you have successfully detected the character dimensions.
- Finally, we will tidy up by using ANSI escape codes to erase all the blocks drawn.
And here’s the code (note: Windows only):
const USER, GDI = "user32.dll", "gdi32.dll"
function cc(library, method, args...)
argtypes = Expr(:tuple, (args .|> a -> isbits(a) ? UInt32 : Ptr{UInt8})...)
@eval ccall(($method, $library), UInt32, $argtypes, $(args...))
end
R = (ansi = 91, matches = (r,g,b) -> r > g+32 && r > b+32)
G = (ansi = 92, matches = (r,g,b) -> g > r+32 && g > b+32)
B = (ansi = 94, matches = (r,g,b) -> b > r+32 && b > g+32)
BLOCKS = (b = rand([G B], 3, 10); b[2,2] = R; b)
XRES, YRES = Tuple(0:1 .|> m -> cc(USER, "GetSystemMetrics", m))
function print_blocks()
S = BLOCKS .|> b -> "\e[1;$(b.ansi)m█"
print.(permutedims(hcat(S, [fill("\n", size(BLOCKS,1)-1); " "]), [2,1]))
flush(stdout)
end
erase_blocks() = print("\e[2K\e[A"^(size(BLOCKS,1)-1), "\e[2K\e[G\e[m")
function get_screen_pixels()
hdc_screen = cc(USER, "GetDC", 0)
hdc_mem = cc(GDI, "CreateCompatibleDC", hdc_screen)
hbitmap = cc(GDI, "CreateCompatibleBitmap", hdc_screen, XRES, YRES)
cc(GDI, "SelectObject", hdc_mem, hbitmap)
cc(GDI, "BitBlt", hdc_mem, 0, 0, XRES, YRES, hdc_screen, 0, 0, 0xcc0020)
pixels = Vector{UInt32}(undef, XRES * YRES)
bmi = UInt32[40, XRES, -YRES, (32<<16) + 1, 0, 0, 0, 0, 0, 0]
cc(GDI, "GetDIBits", hdc_mem, hbitmap, 0, YRES, pixels, bmi, 0)
pixels
end
function search_blocks(pixels)
px(x, y) = pixels[x + XRES*(y-1)]
rgb(x, y) = (c = px(x, y); ((c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff))
block_matches_pixel(c, r, x, y) = BLOCKS[r,c].matches(rgb(x, y)...)
block_matches(c, r, x, y, w, h) = all(1 ≤ u ≤ XRES && 1 ≤ v ≤ YRES &&
block_matches_pixel(c, r, u, v) for u = x:x+w-1, v = y:y+h-1)
for y = 2:YRES-1, x = 2:XRES-1
# test if the 4 pixels around (x,y) match the top-left 4 blocks
if all(block_matches_pixel(2-j, 2-k, x-j, y-k) for j=0:1, k=0:1)
# match found! now calculate block size (w,h)
w = h = 1
while x+w ≤ XRES && block_matches_pixel(2, 2, x+w, y) w += 1 end
while y+h ≤ YRES && block_matches_pixel(2, 2, x, y+h) h += 1 end
# finally test if ALL blocks match color and block size
w > 2 && h > 2 && all(block_matches(j, k, x+(j-2)*w, y+(k-2)*h, w, h) for j=1:size(BLOCKS,2), k=1:size(BLOCKS,1)) &&
return (w=w, h=h)
end
end
nothing
end
function get_character_dimensions()
print_blocks()
sleep(0.1) # short delay to ensure output is really flushed
pixels = get_screen_pixels()
erase_blocks()
dims = search_blocks(pixels)
end
dims = get_character_dimensions()
println("Character dimensions: $(dims ≡ nothing ? "not found" : "$(dims.w) x $(dims.h) pixels")")