From 012e3446f4d7ecc6e30a0c6c94562b2457cad958 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Sun, 19 Dec 2021 22:29:31 +0100 Subject: [PATCH] support sixel --- src/UnicodePlots.jl | 4 +- src/canvas/imgcanvas.jl | 41 +++++++- src/plot.jl | 219 ++++++++++++++++++++-------------------- 3 files changed, 151 insertions(+), 113 deletions(-) diff --git a/src/UnicodePlots.jl b/src/UnicodePlots.jl index 009976eb..1956440b 100644 --- a/src/UnicodePlots.jl +++ b/src/UnicodePlots.jl @@ -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 diff --git a/src/canvas/imgcanvas.jl b/src/canvas/imgcanvas.jl index 67e1a787..4d84d23a 100644 --- a/src/canvas/imgcanvas.jl +++ b/src/canvas/imgcanvas.jl @@ -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 diff --git a/src/plot.jl b/src/plot.jl index 5fb118d3..2daacb5d 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -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