Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework structure for 8bit -> 24bit mode #238

Merged
merged 16 commits into from
Mar 23, 2022
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ jobs:
- uses: julia-actions/julia-runtest@v1
env:
JULIA_DEBUG: 'Main,UnicodePlots'
COLORTERM: 'yes' # 8bit - 256 colors
- uses: julia-actions/julia-runtest@v1
env:
JULIA_DEBUG: 'Main,UnicodePlots'
COLORTERM: 'truecolor' # 24bit
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,16 @@ For a `Jupyter` notebook with the `IJulia` kernel see [here](https://juliamono.n

You can pass `fix_ar=true` to `spy` or `heatmap` in order to recover a unit aspect ratio (this keyword is experimental and might be unnecessary in future versions).

## Color mode

When the `COLORTERM` environment variable is set to either `24bit` or `truecolor`, `UnicodePlots` will use [24bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit) as opposed to [8bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) or even [4bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit) for named colors.

One can force a specific colormode using either `UnicodePlots.truecolors!()` or `UnicodePlots.colors256!()`.

Named colors such as `:red` or `:light_red` will use `256` color values (rendering will be terminal dependent). In order to force named colors to use true colors instead, use `UnicodePlots.USE_LUT[]=true`.

The default color cycle can be changed to bright (high intensity) colors using `UnicodePlots.brightcolors!()` instead of the default `UnicodePlots.faintcolors!()`.

## Low-level Interface

The primary structures that do all the heavy lifting behind the curtain are subtypes of `Canvas`. A canvas is a graphics object for rasterized plotting. Basically it uses Unicode characters to represent pixel.
Expand Down
10 changes: 10 additions & 0 deletions docs/generate_docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,16 @@ For a `Jupyter` notebook with the `IJulia` kernel see [here](https://juliamono.n

You can pass `fix_ar=true` to `spy` or `heatmap` in order to recover a unit aspect ratio (this keyword is experimental and might be unnecessary in future versions).

## Color mode

When the `COLORTERM` environment variable is set to either `24bit` or `truecolor`, `UnicodePlots` will use [24bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit) as opposed to [8bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) or even [4bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit) for named colors.

One can force a specific colormode using either `UnicodePlots.truecolors!()` or `UnicodePlots.colors256!()`.

Named colors such as `:red` or `:light_red` will use `256` color values (rendering will be terminal dependent). In order to force named colors to use true colors instead, use `UnicodePlots.USE_LUT[]=true`.

The default color cycle can be changed to bright (high intensity) colors using `UnicodePlots.brightcolors!()` instead of the default `UnicodePlots.faintcolors!()`.

## Low-level Interface

The primary structures that do all the heavy lifting behind the curtain are subtypes of `Canvas`. A canvas is a graphics object for rasterized plotting. Basically it uses Unicode characters to represent pixel.
Expand Down
1 change: 1 addition & 0 deletions src/UnicodePlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export GraphicsArea,
savefig

include("common.jl")
include("lut.jl")

include("graphics.jl")
include("graphics/bargraphics.jl")
Expand Down
23 changes: 9 additions & 14 deletions src/canvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -287,39 +287,34 @@ function printcolorbarrow(
blank::Char,
)
b = BORDERMAP[border]
bc = BORDER_COLOR[]
min_z, max_z = lim
label = ""
if row == 1
label = lim_str[2]
# print top border and maximum z value
print_color(BORDER_COLOR[], io, b[:tl], b[:t], b[:t], b[:tr])
print_color(io, bc, b[:tl], b[:t], b[:t], b[:tr])
print(io, plot_padding)
print_color(BORDER_COLOR[], io, label)
print_color(io, bc, label)
elseif row == nrows(c)
label = lim_str[1]
# print bottom border and minimum z value
print_color(BORDER_COLOR[], io, b[:bl], b[:b], b[:b], b[:br])
print_color(io, bc, b[:bl], b[:b], b[:b], b[:br])
print(io, plot_padding)
print_color(BORDER_COLOR[], io, label)
print_color(io, bc, label)
else
# print gradient
print_color(BORDER_COLOR[], io, b[:l])
print_color(io, bc, b[:l])
if min_z == max_z # if min and max are the same, single color
fgcol = bgcol = colormap(1, 1, 1)
else # otherwise, blend from min to max
n = 2(nrows(c) - 2)
r = row - 2
bgcol = colormap(n - 2r, 1, n)
fgcol = colormap(n - 2r - 1, 1, n)
bgcol = colormap(n - 2r, 1, n)
end
print(
io,
Crayon(foreground = fgcol, background = bgcol),
HALF_BLOCK,
HALF_BLOCK,
Crayon(reset = true),
)
print_color(BORDER_COLOR[], io, b[:r])
print_color(io, fgcol, HALF_BLOCK, HALF_BLOCK; bgcol = bgcol)
print_color(io, bc, b[:r])
print(io, plot_padding)
# print z label
if row == div(nrows(c), 2) + 1
Expand Down
2 changes: 1 addition & 1 deletion src/canvas/asciicanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function char_point!(
)
if checkbounds(Bool, c.grid, char_x, char_y)
c.grid[char_x, char_y] = n_ascii + char
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
end
c
end
2 changes: 1 addition & 1 deletion src/canvas/blockcanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function char_point!(
)
if checkbounds(Bool, c.grid, char_x, char_y)
c.grid[char_x, char_y] = n_block + char
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
end
c
end
8 changes: 4 additions & 4 deletions src/canvas/braillecanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function BrailleCanvas(
pixel_width = char_width * x_pixel_per_char(BrailleCanvas)
pixel_height = char_height * y_pixel_per_char(BrailleCanvas)
grid = fill(Char(BLANK_BRAILLE), char_width, char_height)
colors = Array{ColorType}(nothing, char_width, char_height)
colors = fill(INVALID_COLOR, char_width, char_height)
BrailleCanvas(
grid,
colors,
Expand Down Expand Up @@ -96,7 +96,7 @@ function pixel!(c::BrailleCanvas, pixel_x::Int, pixel_y::Int, color::UserColorTy
if BLANK_BRAILLE <= (val = UInt64(c.grid[char_x, char_y])) <= FULL_BRAILLE
c.grid[char_x, char_y] = Char(val | UInt64(braille_signs[char_x_off, char_y_off]))
end
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
c
end

Expand All @@ -109,7 +109,7 @@ function char_point!(
)
if checkbounds(Bool, c.grid, char_x, char_y)
c.grid[char_x, char_y] = char
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
end
c
end
Expand All @@ -118,7 +118,7 @@ function printrow(io::IO, c::BrailleCanvas, row::Int)
0 < row <= nrows(c) || throw(ArgumentError("Argument row out of bounds: $row"))
y = row
for x in 1:ncols(c)
print_color(c.colors[x, y], io, c.grid[x, y])
print_color(io, c.colors[x, y], c.grid[x, y])
end
nothing
end
6 changes: 3 additions & 3 deletions src/canvas/densitycanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function DensityCanvas(
pixel_width = char_width * x_pixel_per_char(DensityCanvas)
pixel_height = char_height * y_pixel_per_char(DensityCanvas)
grid = fill(UInt(0), char_width, char_height)
colors = Array{ColorType}(nothing, char_width, char_height)
colors = fill(INVALID_COLOR, char_width, char_height)
DensityCanvas(
grid,
colors,
Expand Down Expand Up @@ -89,7 +89,7 @@ function pixel!(c::DensityCanvas, pixel_x::Int, pixel_y::Int, color::UserColorTy
char_x, char_y = pixel_to_char_point(c, pixel_x, pixel_y)
c.grid[char_x, char_y] += 1
c.max_density = max(c.max_density, c.grid[char_x, char_y])
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
c
end

Expand All @@ -101,7 +101,7 @@ function printrow(io::IO, c::DensityCanvas, row::Int)
val_scale = (den_sign_count - 1) / c.max_density
for x in 1:ncols(c)
den_index = round(Int, c.grid[x, y] * val_scale, RoundNearestTiesUp) + 1
print_color(c.colors[x, y], io, signs[den_index])
print_color(io, c.colors[x, y], signs[den_index])
end
nothing
end
2 changes: 1 addition & 1 deletion src/canvas/dotcanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function char_point!(
)
if checkbounds(Bool, c.grid, char_x, char_y)
c.grid[char_x, char_y] = n_dot + char
set_color!(c.colors, char_x, char_y, crayon_256_color(color), c.blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), c.blend)
end
c
end
41 changes: 16 additions & 25 deletions src/canvas/heatmapcanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ const HALF_BLOCK = '▄'

@inline nrows(c::HeatmapCanvas) = div(size(grid(c), 2) + 1, 2)

HeatmapCanvas(args...; kw...) = CreateLookupCanvas(
HeatmapCanvas,
UInt8,
(0, 1),
args...;
min_char_width = 1,
min_char_height = 1,
kw...,
)

_toCrayon(c) = c === nothing ? 0 : (c isa Unsigned ? Int(c) : c)
function HeatmapCanvas(args...; kw...)
c = CreateLookupCanvas(
HeatmapCanvas,
UInt8,
(0, 1),
args...;
min_char_width = 1,
min_char_height = 1,
kw...,
)
fill!(c.colors, ansi_color(:black)) # black background by default
c
end

function printrow(io::IO, c::HeatmapCanvas, row::Int)
0 < row <= nrows(c) || throw(ArgumentError("Argument row out of bounds: $row"))
Expand All @@ -49,22 +51,11 @@ function printrow(io::IO, c::HeatmapCanvas, row::Int)
# extend the plot upwards by half a row
isodd(size(grid(c), 2)) && (y -= 1)

iscolor = get(io, :color, false)
for x in 1:ncols(c)
if iscolor
fgcol = _toCrayon(c.colors[x, y])
if y > 1
bgcol = _toCrayon(c.colors[x, y - 1])
print(io, Crayon(foreground = fgcol, background = bgcol), HALF_BLOCK)
# for odd numbers of rows, only print the foreground for the top row
else
print(io, Crayon(foreground = fgcol), HALF_BLOCK)
end
else
print(io, HALF_BLOCK)
end
# for odd numbers of rows, only print the foreground for the top row
bgcol = y > 1 ? c.colors[x, y - 1] : missing
print_color(io, c.colors[x, y], HALF_BLOCK; bgcol = bgcol)
end
iscolor && print(io, Crayon(reset = true))

nothing
end
6 changes: 3 additions & 3 deletions src/canvas/lookupcanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function CreateLookupCanvas(
pixel_width = char_width * x_pixel_per_char(T)
pixel_height = char_height * y_pixel_per_char(T)
grid = fill(G(0), char_width, char_height)
colors = Array{ColorType}(nothing, char_width, char_height)
colors = fill(INVALID_COLOR, char_width, char_height)
T(
grid,
colors,
Expand Down Expand Up @@ -102,15 +102,15 @@ function pixel!(
grid(c)[char_x, char_y] |= lookup_encode(c)[char_x_off, char_y_off]
end
blend = color isa Symbol && c.blend # don't attempt to blend colors if they have been explicitly specified
set_color!(c.colors, char_x, char_y, crayon_256_color(color), blend)
set_color!(c.colors, char_x, char_y, ansi_color(color), blend)
c
end

function printrow(io::IO, c::LookupCanvas, row::Int)
0 < row <= nrows(c) || throw(ArgumentError("Argument row out of bounds: $row"))
y = row
for x in 1:ncols(c)
print_color(colors(c)[x, y], io, lookup_decode(c)[grid(c)[x, y] + 1])
print_color(io, colors(c)[x, y], lookup_decode(c)[grid(c)[x, y] + 1])
end
nothing
end
19 changes: 4 additions & 15 deletions src/colormaps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1405,19 +1405,8 @@ const COLOR_MAP_DATA = Dict(
:jet => _jet_data,
)

const _cube_colors = [0, 95, 135, 175, 215, 255]

if VERSION >= v"1.7"
closest_cube_color(c) = argmin(abs(c - v) for v in _cube_colors) - 1
else
closest_cube_color(c) = argmin(map(v -> abs(c - v), _cube_colors)) - 1
end

"returns the closest ansi color code from the 6x6x6 cube, in the range 16 - 231"
function rgb2ansi(rgb)
r, g, b = (closest_cube_color(round(Int, 255c)) for c in rgb)
16 + 36r + 6g + b
end
c256(c::AbstractFloat) = round(Int, 255c)
c256(c::Integer) = c

function cmapcolor(z, minz, maxz, cmap)
isfinite(z) || return nothing
Expand All @@ -1428,7 +1417,7 @@ function cmapcolor(z, minz, maxz, cmap)
else
1 + round(Int, ((z - minz) / (maxz - minz)) * (length(cmap) - 1))
end
rgb2ansi(cmap[i])
ansi_color(c256.(cmap[i]))
end

function colormap_callback(cmap::Symbol)
Expand All @@ -1440,4 +1429,4 @@ colormap_callback(cmap::AbstractVector) = (z, minz, maxz) -> cmapcolor(z, minz,
colormap_callback(cmap::Nothing) = nothing
colormap_callback(cmap::Function) = cmap

rgbimgcolor(z) = rgb2ansi((z.r, z.g, z.b))
rgbimgcolor(z) = ansi_color((c256(z.r), c256(z.g), c256(z.b)))
Loading