Skip to content

Commit

Permalink
add imageplot (support sixel) (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
t-bltg authored Sep 2, 2022
1 parent 7af02f3 commit a03412b
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 31 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [3.1] - 2022-09-02
### Added
- `imageplot`.
- sixel support in `ImageGraphics`.

## [3.0] - 2022-06-24
### Added
- Support multiple series (`Matrix` columns) on `lineplot` and `scatterplot` (and mutating versions).
Expand Down
10 changes: 7 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,38 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MarchingCubes = "299715c1-40a9-479a-aaf9-4a633d36f717"
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[compat]
ColorSchemes = "3.19"
ColorSchemes = "^3.19"
ColorTypes = "0.11"
Contour = "0.5 - 0.6"
Crayons = "4.1"
Crayons = "^4.1"
FileIO = "1"
FreeTypeAbstraction = "0.9 - 0.10"
LazyModules = "0.3"
MarchingCubes = "0.1"
NaNMath = "0.3, 1"
Requires = "1"
StaticArrays = "1"
StatsBase = "0.33"
Unitful = "1"
julia = "1.6"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"

[targets]
test = ["Aqua", "ImageMagick", "Random", "ReferenceTests", "StableRNGs", "Test", "TimerOutputs"]
test = ["Aqua", "ImageInTerminal", "ImageMagick", "Random", "ReferenceTests", "StableRNGs", "Test", "TestImages", "TimerOutputs"]
4 changes: 3 additions & 1 deletion src/UnicodePlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ using ColorSchemes
using StaticArrays
using LazyModules
using ColorTypes
using Requires
using Crayons
using Printf
using Dates
Expand Down Expand Up @@ -74,14 +75,15 @@ export scatterplot!,
stairs

# methods without mutating variants
export horizontal_histogram, vertical_histogram, histogram, heatmap, spy
export horizontal_histogram, vertical_histogram, histogram, heatmap, spy, imageplot

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

include("graphics.jl")
include("graphics/bargraphics.jl")
include("graphics/boxgraphics.jl")
include("graphics/imagegraphics.jl")

include("canvas.jl")
include("canvas/lookupcanvas.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/canvas/densitycanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function pixel!(c::DensityCanvas, pixel_x::Integer, pixel_y::Integer, color::Use
c
end

function preprocess!(c::DensityCanvas)
function preprocess!(::IO, c::DensityCanvas)
c.max_density[] = max(eps(), maximum(c.dscale, c.grid))
c -> c.max_density[] = -Inf
end
Expand Down
7 changes: 7 additions & 0 deletions src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ terminal_24bit() = lowercase(get(ENV, "COLORTERM", "")) ∈ ("24bit", "truecolor
forced_24bit() = lowercase(get(ENV, "UP_COLORMODE", "")) ("24", "24bit", "truecolor")
forced_8bit() = lowercase(get(ENV, "UP_COLORMODE", "")) ("8", "8bit")

imageplot(args...; kwargs...) =
error("not implemented, did you forget 'using ImageInTerminal' ?")

function __init__()
if (terminal_24bit() || forced_24bit()) && !forced_8bit()
truecolors!()
Expand All @@ -246,6 +249,10 @@ function __init__()
colors256!()
faintcolors!()
end
@require ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" begin
imageplot(img::AbstractArray{<:Colorant}; kwargs...) =
Plot(ImageGraphics(img); kwargs...)
end
nothing
end

Expand Down
6 changes: 3 additions & 3 deletions src/graphics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ suitable_color(c::GraphicsArea, color::Union{UserColorType,AbstractVector}) = an
Base.print(io::IO, c::GraphicsArea) = _print(io, print, print_color, c)

function _print(io::IO, print_nocol, print_color, c::GraphicsArea)
postprocess! = preprocess!(c)
postprocess! = preprocess!(io, c)
for row 1:nrows(c)
print_row(io, print_nocol, print_color, c, row)
row < nrows(c) && print_nocol(io, '\n')
Expand All @@ -26,7 +26,7 @@ function _show(io::IO, print_nocol, print_color, c::GraphicsArea)
bc = BORDER_COLOR[]
border_length = ncols(c)
print_border(io, print_nocol, print_color, :t, border_length, nothing, '\n', b, bc)
postprocess! = preprocess!(c)
postprocess! = preprocess!(io, c)
for row 1:nrows(c)
print_color(io, bc, b[:l])
print_row(io, print_nocol, print_color, c, row)
Expand All @@ -46,4 +46,4 @@ print_row(io::IO, c::GraphicsArea, row::Integer) = print_row(io, print, print_co
Optional step: pre-process canvas before printing rows (e.g. for costly computations).
Returns a cleanup callback for optional cleanup after printing.
"""
preprocess!(c::GraphicsArea) = c -> nothing
preprocess!(::IO, c::GraphicsArea) = c -> nothing
2 changes: 1 addition & 1 deletion src/graphics/bargraphics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function addrow!(
c
end

function preprocess!(c::BarplotGraphics)
function preprocess!(::IO, c::BarplotGraphics)
max_val, i = findmax(c.xscale.(c.bars))
c.max_val[] = max(max_val, c.maximum)
c.max_len[] = length(string(c.bars[i]))
Expand Down
49 changes: 49 additions & 0 deletions src/graphics/imagegraphics.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
struct ImageGraphics{C<:Colorant} <: GraphicsArea
img::Matrix{C}
sixel::RefValue{Bool}
visible::Bool
encoded_size::Vector{Int}
ren::Vector{String}
end

ImageGraphics(img::AbstractArray{<:Colorant}) =
ImageGraphics(img, Ref(false), true, [0, 0], String[])

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

function preprocess!(io::IO, c::ImageGraphics)
ctx = IOContext(PipeBuffer(), :displaysize => displaysize(io))
c.sixel[] = false
char_h = char_w = nothing # determine the terminal caret size, in pixels
if ImageInTerminal.choose_sixel(c.img)
ans = ImageInTerminal.Sixel.TerminalTools.query_terminal("\e[16t", stdout)
if ans isa String && (m = match(r"\e\[6;(\d+);(\d+)t", ans)) nothing
char_h, char_w = tryparse.(Int, m.captures)
c.sixel[] = char_h nothing && char_w nothing
end
end
if c.sixel[]
h, w = size(c.img)
lines = String[]
for r 1:char_h:h
ImageInTerminal.sixel_encode(ctx, c.img[r:min(r + char_h - 1, h), :])
push!(lines, read(ctx, String))
end
nc = ceil(Int, w / char_w)
else
ImageInTerminal.imshow(ctx, c.img)
lines = readlines(ctx)
nc = length(no_ansi_escape(first(lines)))
end
c.encoded_size .= length(lines), nc
resize!(c.ren, nrows(c))
copyto!(c.ren, lines)
c -> nothing
end

function print_row(io::IO, print_nocol, _, c::ImageGraphics, row::Integer)
0 < row nrows(c) || throw(ArgumentError("`row` out of bounds: $row"))
print_nocol(io, c.ren[row])
nothing
end
44 changes: 25 additions & 19 deletions src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,22 @@ Base.show(io::IO, p::Plot) = _show(io, print, print_color, p)
function _show(end_io::IO, print_nocol, print_color, p::Plot)
buf = PipeBuffer() # buffering, for performance
io_color = get(end_io, :color, false)
io = IOContext(buf, :color => io_color)
io = IOContext(buf, :color => io_color, :displaysize => displaysize(end_io))

c = p.graphics
🗷 = Char(BLANK) # blank outside canvas
🗹 = blank(c) # blank inside canvas
g = p.graphics
🗷 = Char(BLANK) # blank outside graphics
🗹 = blank(g) # blank inside graphics
############################################################
# 🗷 = 'x' # debug
# 🗹 = Char(typeof(c) <: BrailleCanvas ? '⠿' : 'o') # debug
# 🗹 = Char(typeof(g) <: BrailleCanvas ? '⠿' : 'o') # debug
############################################################
nr = nrows(c)
nc = ncols(c)
p_width = nc + 2 # left corner + border length (number of canvas cols) + right corner
postprocess! = preprocess!(io, g)

bmap = BORDERMAP[p.border :none && c isa BrailleCanvas ? :bnone : p.border]
nr = nrows(g)
nc = ncols(g)
p_width = nc + 2 # left corner + border length (number of graphics cols) + right corner

bmap = BORDERMAP[p.border :none && g isa BrailleCanvas ? :bnone : p.border]
bc = BORDER_COLOR[]

# get length of largest strings to the left and right
Expand All @@ -166,7 +168,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
max_len_l += length(ylabel(p)) + 1
end

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

# padding-string from left to border
Expand Down Expand Up @@ -210,7 +212,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
🗹 * border_right_pad * '\n',
🗹,
)
c.visible && print_border(
g.visible && print_border(
io,
print_nocol,
print_color,
Expand All @@ -224,8 +226,6 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
# compute position of ylabel
y_lab_row = round(nr / 2, RoundNearestTiesUp)

postprocess! = preprocess!(c)

# plot all rows
for row 1:nr
# print left annotations
Expand Down Expand Up @@ -255,12 +255,18 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
# print the left annotation
print_color(io, left_col, left_str)
end
if c.visible
if g.visible
# print left border
print_nocol(io, plot_padding)
print_color(io, bc, bmap[:l])
# print canvas row
print_row(io, print_nocol, print_color, c, row)
print_row(io, print_nocol, print_color, g, row)
if g isa ImageGraphics && g.sixel[]
offset = plot_offset + nc + 1
# 1F: move cursor to the beginning of the previous line, 1 line up
# $(offset)C: move cursor to the right by an amount of $offset columns
write(io, "\e[1F\e[$(offset)C")
end
# print right label and padding
print_color(io, bc, bmap[:r])
end
Expand All @@ -276,7 +282,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
io,
print_nocol,
print_color,
c,
g,
row,
p.cmap,
min_max_z_str,
Expand All @@ -286,13 +292,13 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
🗷,
)
end
row < nrows(c) && print_nocol(io, '\n')
row < nrows(g) && print_nocol(io, '\n')
end

postprocess!(c)
postprocess!(g)

# draw bottom border
c.visible && print_border(
g.visible && print_border(
io,
print_nocol,
print_color,
Expand Down
Loading

0 comments on commit a03412b

Please sign in to comment.