Skip to content

Commit

Permalink
support sixel
Browse files Browse the repository at this point in the history
  • Loading branch information
t-bltg committed Dec 19, 2021
1 parent 848a9c0 commit 012e344
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 113 deletions.
4 changes: 2 additions & 2 deletions src/UnicodePlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ include("interface/heatmap.jl")
include("interface/spy.jl")
include("interface/boxplot.jl")

imshow(args...) = @warn "not implemented, did you forget 'using ImageInTerminal' before 'using UnicodePlots' ?"
image(args...) = @warn "not implemented, did you forget 'using ImageInTerminal' ?"

function __init__()
@require ImageInTerminal="d8c32880-2388-543b-8c61-d9f865259254" begin
imshow(img::AbstractArray{<:Colorant}; kwargs...) = Plot(ImgCanvas(img); kwargs...)
image(img::AbstractArray{<:Colorant}; kwargs...) = Plot(ImgCanvas(img); kwargs...)
end
end

Expand Down
41 changes: 39 additions & 2 deletions src/canvas/imgcanvas.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
struct ImgCanvas <: Canvas
img::AbstractArray{<:Colorant}
ren::Vector{String}
encoded_size::Vector{Int}
sixel::Ref
end

@inline nrows(c::ImgCanvas) = size(c.img, 1)
@inline ncols(c::ImgCanvas) = size(c.img, 2)
function ImgCanvas(img::AbstractArray{<:Colorant})
render(ImgCanvas(img, String[], [0, 0], Ref(false)))
end

@inline nrows(c::ImgCanvas) = c.encoded_size[1]
@inline ncols(c::ImgCanvas) = c.encoded_size[2]

function render(c::ImgCanvas)
if ImageInTerminal.use_sixel(c.img)
c.sixel[] = true
lines = String[]
h, w = size(c.img)
char_pixels = ImageInTerminal.Sixel.TerminalTools.query_terminal("\e[16t", r"\e\[6;(\d+);(\d+)t", stdout)
char_h, char_w = length(char_pixels) > 1 ? parse.(Int, char_pixels) : (15, 7)
for r 1:char_h:h
io = IOBuffer()
ImageInTerminal.sixel_encode(io, c.img[r:min(r + char_h - 1, h), :])
push!(lines, String(take!(io)))
end
copyto!(c.encoded_size, [length(lines), ceil(Int, w / char_w)])
else
io = PipeBuffer()
ImageInTerminal.imshow(io, c.img, ImageInTerminal.colormode[1])
lines = readlines(io)
copyto!(c.encoded_size, [length(lines), length(replace(first(lines), r"\x1B\[[0-9;]*[a-zA-Z]" => ""))])
end
resize!(c.ren, nrows(c))
copyto!(c.ren, lines)
c
end

function printrow(io::IO, c::ImgCanvas, row::Int)
0 < row <= nrows(c) || throw(ArgumentError("Argument row out of bounds: $row"))
write(io, c.ren[row])
nothing
end
219 changes: 110 additions & 109 deletions src/plot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -515,127 +515,128 @@ function print_labels(
end

function Base.show(io::IO, p::Plot)
if (c = p.graphics) isa ImgCanvas
ImageInTerminal.imshow(io, c.img)
c = p.graphics
🗷 = Char(0x0020) # blank outside canvas
🗹 = Char(c isa BrailleCanvas ? 0x2800 : 🗷) # blank inside canvas
############################################################
# 🗷 = 'x' # debug
# 🗹 = Char(typeof(c) <: BrailleCanvas ? '⠿' : 'o') # debug
############################################################
border_length = ncols(c)
p_width = border_length + 2 # left corner + border + right corner

bmap = bordermap[p.border === :none && c isa BrailleCanvas ? :bnone : p.border]

# get length of largest strings to the left and right
max_len_l = if p.show_labels && !isempty(p.labels_left)
maximum([length(_nocolor_string(l)) for l in values(p.labels_left)])
else
🗷 = Char(0x0020) # blank outside canvas
🗹 = Char(c isa BrailleCanvas ? 0x2800 : 🗷) # blank inside canvas
############################################################
# 🗷 = 'x' # debug
# 🗹 = Char(typeof(c) <: BrailleCanvas ? '⠿' : 'o') # debug
############################################################
border_length = ncols(c)
p_width = border_length + 2 # left corner + border + right corner

bmap = bordermap[p.border === :none && c isa BrailleCanvas ? :bnone : p.border]

# get length of largest strings to the left and right
max_len_l = if p.show_labels && !isempty(p.labels_left)
maximum([length(_nocolor_string(l)) for l in values(p.labels_left)])
else
0
end
max_len_r = if p.show_labels && !isempty(p.labels_right)
maximum([length(_nocolor_string(l)) for l in values(p.labels_right)])
else
0
end
if !p.compact && p.show_labels && p.ylabel != ""
max_len_l += length(p.ylabel) + 1
end
0
end
max_len_r = if p.show_labels && !isempty(p.labels_right)
maximum([length(_nocolor_string(l)) for l in values(p.labels_right)])
else
0
end
if !p.compact && p.show_labels && p.ylabel != ""
max_len_l += length(p.ylabel) + 1
end

# offset where the plot (incl border) begins
plot_offset = max_len_l + p.margin + p.padding
# offset where the plot (incl border) begins
plot_offset = max_len_l + p.margin + p.padding

# padding-string from left to border
plot_padding = repeat(🗷, p.padding)
# padding-string from left to border
plot_padding = repeat(🗷, p.padding)

if p.show_colorbar
min_z, max_z = p.colorbar_lim
min_z_str = string(isinteger(min_z) ? min_z : float_round_log10(min_z))
max_z_str = string(isinteger(max_z) ? max_z : float_round_log10(max_z))
cbar_max_len = max(length(min_z_str), length(max_z_str), length(_nocolor_string(p.zlabel)))
cbar_pad = plot_padding * repeat(🗹, 4) * plot_padding * repeat(🗷, cbar_max_len)
else
cbar_pad = ""
end
if p.show_colorbar
min_z, max_z = p.colorbar_lim
min_z_str = string(isinteger(min_z) ? min_z : float_round_log10(min_z))
max_z_str = string(isinteger(max_z) ? max_z : float_round_log10(max_z))
cbar_max_len = max(length(min_z_str), length(max_z_str), length(_nocolor_string(p.zlabel)))
cbar_pad = plot_padding * repeat(🗹, 4) * plot_padding * repeat(🗷, cbar_max_len)
else
cbar_pad = ""
end

# padding-string between labels and border
border_left_pad = repeat(🗷, plot_offset)
# padding-string between labels and border
border_left_pad = repeat(🗷, plot_offset)

# trailing
border_right_pad = repeat(🗷, max_len_r) * plot_padding * cbar_pad
# trailing
border_right_pad = repeat(🗷, max_len_r) * plot_padding * cbar_pad

# plot the title and the top border
print_title(
io, border_left_pad, p.title, border_right_pad * '\n', 🗹;
p_width = p_width, color = :bold
)
print_labels(io, :t, p, border_length - 2, border_left_pad * 🗹, 🗹 * border_right_pad * '\n', 🗹)
print_border(io, :t, border_length, border_left_pad, border_right_pad * '\n', bmap)

# compute position of ylabel
y_lab_row = round(nrows(c) / 2, RoundNearestTiesUp)

# plot all rows
for row in 1:nrows(c)
# Current labels to left and right of the row and their length
left_str = get(p.labels_left, row, "")
left_col = get(p.colors_left, row, :light_black)
right_str = get(p.labels_right, row, "")
right_col = get(p.colors_right, row, :light_black)
left_len = length(_nocolor_string(left_str))
right_len = length(_nocolor_string(right_str))
if !get(io, :color, false)
left_str = _nocolor_string(left_str)
right_str = _nocolor_string(right_str)
end
# print left annotations
print(io, repeat(🗷, p.margin))
if p.show_labels
if !p.compact && row == y_lab_row
# print ylabel
print_color(:normal, io, p.ylabel)
print(io, repeat(🗷, max_len_l - length(p.ylabel) - left_len))
else
# print padding to fill ylabel length
print(io, repeat(🗷, max_len_l - left_len))
end
# print the left annotation
print_color(left_col, io, left_str)
end
# print left border
print(io, plot_padding)
print_color(:light_black, io, bmap[:l])
# print canvas row
printrow(io, c, row)
# print right label and padding
print_color(:light_black, io, bmap[:r])
if p.show_labels
print(io, plot_padding)
print_color(right_col, io, right_str)
print(io, repeat(🗷, max_len_r - right_len))
end
# print colorbar
if p.show_colorbar
print(io, plot_padding)
printcolorbarrow(
io, c, row, p.colormap, p.colorbar_border, p.colorbar_lim,
(min_z_str, max_z_str), plot_padding, p.zlabel, cbar_max_len, 🗷
)
# plot the title and the top border
print_title(
io, border_left_pad, p.title, border_right_pad * '\n', 🗹;
p_width = p_width, color = :bold
)
print_labels(io, :t, p, border_length - 2, border_left_pad * 🗹, 🗹 * border_right_pad * '\n', 🗹)
print_border(io, :t, border_length, border_left_pad, border_right_pad * '\n', bmap)

# compute position of ylabel
y_lab_row = round(nrows(c) / 2, RoundNearestTiesUp)

# plot all rows
for row in 1:nrows(c)
# Current labels to left and right of the row and their length
left_str = get(p.labels_left, row, "")
left_col = get(p.colors_left, row, :light_black)
right_str = get(p.labels_right, row, "")
right_col = get(p.colors_right, row, :light_black)
left_len = length(_nocolor_string(left_str))
right_len = length(_nocolor_string(right_str))
if !get(io, :color, false)
left_str = _nocolor_string(left_str)
right_str = _nocolor_string(right_str)
end
# print left annotations
print(io, repeat(🗷, p.margin))
if p.show_labels
if !p.compact && row == y_lab_row
# print ylabel
print_color(:normal, io, p.ylabel)
print(io, repeat(🗷, max_len_l - length(p.ylabel) - left_len))
else
# print padding to fill ylabel length
print(io, repeat(🗷, max_len_l - left_len))
end
row < nrows(c) && println(io)
# print the left annotation
print_color(left_col, io, left_str)
end

# draw bottom border and bottom labels
print_border(io, :b, border_length, '\n' * border_left_pad, border_right_pad, bmap)
# print left border
print(io, plot_padding)
print_color(:light_black, io, bmap[:l])
# print canvas row
printrow(io, c, row)
if c isa ImgCanvas && c.sixel[]
offset = plot_offset + border_length + 1
write(io, "\e[A\e[$(offset)C")
end
# print right label and padding
print_color(:light_black, io, bmap[:r])
if p.show_labels
print_labels(io, :b, p, border_length - 2, '\n' * border_left_pad * 🗹, 🗹 * border_right_pad, 🗹)
p.compact || print_title(
io, '\n' * border_left_pad, p.xlabel, border_right_pad, 🗹;
p_width = p_width
print(io, plot_padding)
print_color(right_col, io, right_str)
print(io, repeat(🗷, max_len_r - right_len))
end
# print colorbar
if p.show_colorbar
print(io, plot_padding)
printcolorbarrow(
io, c, row, p.colormap, p.colorbar_border, p.colorbar_lim,
(min_z_str, max_z_str), plot_padding, p.zlabel, cbar_max_len, 🗷
)
end
row < nrows(c) && println(io)
end

# draw bottom border and bottom labels
print_border(io, :b, border_length, '\n' * border_left_pad, border_right_pad, bmap)
if p.show_labels
print_labels(io, :b, p, border_length - 2, '\n' * border_left_pad * 🗹, 🗹 * border_right_pad, 🗹)
p.compact || print_title(
io, '\n' * border_left_pad, p.xlabel, border_right_pad, 🗹;
p_width = p_width
)
end
nothing
end
Expand Down

0 comments on commit 012e344

Please sign in to comment.